// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Resources;
namespace ICSharpCode.Core
{
///
/// Static class containing the AddInTree. Contains methods for accessing tree nodes and building items.
///
public static class AddInTree
{
static List addIns = new List();
static AddInTreeNode rootNode = new AddInTreeNode();
static ConcurrentDictionary doozers = new ConcurrentDictionary();
static ConcurrentDictionary conditionEvaluators = new ConcurrentDictionary();
static AddInTree()
{
doozers.TryAdd("Class", new ClassDoozer());
doozers.TryAdd("FileFilter", new FileFilterDoozer());
doozers.TryAdd("String", new StringDoozer());
doozers.TryAdd("Icon", new IconDoozer());
doozers.TryAdd("MenuItem", new MenuItemDoozer());
doozers.TryAdd("ToolbarItem", new ToolbarItemDoozer());
doozers.TryAdd("Include", new IncludeDoozer());
conditionEvaluators.TryAdd("Compare", new CompareConditionEvaluator());
conditionEvaluators.TryAdd("Ownerstate", new OwnerStateConditionEvaluator());
ApplicationStateInfoService.RegisterStateGetter("Installed 3rd party AddIns", GetInstalledThirdPartyAddInsListAsString);
}
static object GetInstalledThirdPartyAddInsListAsString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (AddIn addIn in AddIns) {
// Skip preinstalled AddIns (show only third party AddIns)
if (addIn.IsPreinstalled)
continue;
if (sb.Length > 0) sb.Append(", ");
sb.Append("[");
sb.Append(addIn.Name);
if (addIn.Version != null) {
sb.Append(' ');
sb.Append(addIn.Version.ToString());
}
if (!addIn.Enabled) {
sb.Append(", Enabled=");
sb.Append(addIn.Enabled);
}
if (addIn.Action != AddInAction.Enable) {
sb.Append(", Action=");
sb.Append(addIn.Action.ToString());
}
sb.Append("]");
}
return sb.ToString();
}
///
/// Gets the list of loaded AddIns.
///
public static IList AddIns {
get {
return addIns.AsReadOnly();
}
}
///
/// Gets a dictionary of registered doozers.
///
public static ConcurrentDictionary Doozers {
get {
return doozers;
}
}
///
/// Gets a dictionary of registered condition evaluators.
///
public static ConcurrentDictionary ConditionEvaluators {
get {
return conditionEvaluators;
}
}
///
/// Checks whether the specified path exists in the AddIn tree.
///
public static bool ExistsTreeNode(string path)
{
return GetTreeNode(path, false) != null;
}
///
/// Gets the representing the specified path.
/// This method throws a when the
/// path does not exist.
///
public static AddInTreeNode GetTreeNode(string path)
{
return GetTreeNode(path, true);
}
///
/// Gets the representing the specified path.
///
/// The path of the AddIn tree node
///
/// If set to true, this method throws a
/// when the path does not exist.
/// If set to false, null is returned for non-existing paths.
///
public static AddInTreeNode GetTreeNode(string path, bool throwOnNotFound)
{
if (path == null || path.Length == 0) {
return rootNode;
}
string[] splittedPath = path.Split('/');
AddInTreeNode curPath = rootNode;
for (int i = 0; i < splittedPath.Length; i++) {
if (!curPath.ChildNodes.TryGetValue(splittedPath[i], out curPath)) {
if (throwOnNotFound)
throw new TreePathNotFoundException(path);
else
return null;
}
}
return curPath;
}
///
/// Builds a single item in the addin tree.
///
/// A path to the item in the addin tree.
/// The owner used to create the objects.
/// The path does not
/// exist or does not point to an item.
public static object BuildItem(string path, object caller)
{
return BuildItem(path, caller, null);
}
public static object BuildItem(string path, object caller, IEnumerable additionalConditions)
{
int pos = path.LastIndexOf('/');
string parent = path.Substring(0, pos);
string child = path.Substring(pos + 1);
AddInTreeNode node = GetTreeNode(parent);
return node.BuildChildItem(child, caller, additionalConditions);
}
///
/// Builds the items in the path. Ensures that all items have the type T.
/// Throws a if the path is not found.
///
/// A path in the addin tree.
/// The owner used to create the objects.
public static List BuildItems(string path, object caller)
{
return BuildItems(path, caller, true);
}
///
/// Builds the items in the path. Ensures that all items have the type T.
///
/// A path in the addin tree.
/// The owner used to create the objects.
/// If true, throws a
/// if the path is not found. If false, an empty ArrayList is returned when the
/// path is not found.
public static List BuildItems(string path, object caller, bool throwOnNotFound)
{
AddInTreeNode node = GetTreeNode(path, throwOnNotFound);
if (node == null)
return new List();
else
return node.BuildChildItems(caller);
}
static AddInTreeNode CreatePath(AddInTreeNode localRoot, string path)
{
if (path == null || path.Length == 0) {
return localRoot;
}
string[] splittedPath = path.Split('/');
AddInTreeNode curPath = localRoot;
int i = 0;
while (i < splittedPath.Length) {
if (!curPath.ChildNodes.ContainsKey(splittedPath[i])) {
curPath.ChildNodes[splittedPath[i]] = new AddInTreeNode();
}
curPath = curPath.ChildNodes[splittedPath[i]];
++i;
}
return curPath;
}
static void AddExtensionPath(ExtensionPath path)
{
AddInTreeNode treePath = CreatePath(rootNode, path.Name);
foreach (IEnumerable innerCodons in path.GroupedCodons)
treePath.AddCodons(innerCodons);
}
///
/// The specified AddIn is added to the collection.
/// If the AddIn is enabled, its doozers, condition evaluators and extension
/// paths are added to the AddInTree and its resources are added to the
/// .
///
public static void InsertAddIn(AddIn addIn)
{
if (addIn.Enabled) {
foreach (ExtensionPath path in addIn.Paths.Values) {
AddExtensionPath(path);
}
foreach (Runtime runtime in addIn.Runtimes) {
if (runtime.IsActive) {
foreach (LazyLoadDoozer doozer in runtime.DefinedDoozers) {
if (!doozers.TryAdd(doozer.Name, doozer))
throw new AddInLoadException("Duplicate doozer: " + doozer.Name);
}
foreach (LazyConditionEvaluator condition in runtime.DefinedConditionEvaluators) {
if (!conditionEvaluators.TryAdd(condition.Name, condition))
throw new AddInLoadException("Duplicate condition evaluator: " + condition.Name);
}
}
}
string addInRoot = Path.GetDirectoryName(addIn.FileName);
foreach(string bitmapResource in addIn.BitmapResources)
{
string path = Path.Combine(addInRoot, bitmapResource);
ResourceManager resourceManager = ResourceManager.CreateFileBasedResourceManager(Path.GetFileNameWithoutExtension(path), Path.GetDirectoryName(path), null);
ResourceService.RegisterNeutralImages(resourceManager);
}
foreach(string stringResource in addIn.StringResources)
{
string path = Path.Combine(addInRoot, stringResource);
ResourceManager resourceManager = ResourceManager.CreateFileBasedResourceManager(Path.GetFileNameWithoutExtension(path), Path.GetDirectoryName(path), null);
ResourceService.RegisterNeutralStrings(resourceManager);
}
}
addIns.Add(addIn);
}
///
/// The specified AddIn is removed to the collection.
/// This is only possible for disabled AddIns, enabled AddIns require
/// a restart of the application to be removed.
///
/// Occurs when trying to remove an enabled AddIn.
public static void RemoveAddIn(AddIn addIn)
{
if (addIn.Enabled) {
throw new ArgumentException("Cannot remove enabled AddIns at runtime.");
}
addIns.Remove(addIn);
}
// As long as the show form takes 10 times of loading the xml representation I'm not implementing
// binary serialization.
// static Dictionary nameLookupTable = new Dictionary();
// static Dictionary addInLookupTable = new Dictionary();
//
// public static ushort GetAddInOffset(AddIn addIn)
// {
// return addInLookupTable[addIn];
// }
//
// public static ushort GetNameOffset(string name)
// {
// if (!nameLookupTable.ContainsKey(name)) {
// nameLookupTable[name] = (ushort)nameLookupTable.Count;
// }
// return nameLookupTable[name];
// }
//
// public static void BinarySerialize(string fileName)
// {
// for (int i = 0; i < addIns.Count; ++i) {
// addInLookupTable[addIns] = (ushort)i;
// }
// using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileName))) {
// rootNode.BinarySerialize(writer);
// writer.Write((ushort)addIns.Count);
// for (int i = 0; i < addIns.Count; ++i) {
// addIns[i].BinarySerialize(writer);
// }
// writer.Write((ushort)nameLookupTable.Count);
// foreach (string name in nameLookupTable.Keys) {
// writer.Write(name);
// }
// }
// }
// used by Load(): disables an addin and removes it from the dictionaries.
static void DisableAddin(AddIn addIn, Dictionary dict, Dictionary addInDict)
{
addIn.Enabled = false;
addIn.Action = AddInAction.DependencyError;
foreach (string name in addIn.Manifest.Identities.Keys) {
dict.Remove(name);
addInDict.Remove(name);
}
}
///
/// Loads a list of .addin files, ensuring that dependencies are satisfied.
/// This method is normally called by .
///
///
/// The list of .addin file names to load.
///
///
/// The list of disabled AddIn identity names.
///
public static void Load(List addInFiles, List disabledAddIns)
{
List list = new List();
Dictionary dict = new Dictionary();
Dictionary addInDict = new Dictionary();
var nameTable = new System.Xml.NameTable();
foreach (string fileName in addInFiles) {
AddIn addIn;
try {
addIn = AddIn.Load(fileName, nameTable);
} catch (AddInLoadException ex) {
LoggingService.Error(ex);
if (ex.InnerException != null) {
MessageService.ShowError("Error loading AddIn " + fileName + ":\n"
+ ex.InnerException.Message);
} else {
MessageService.ShowError("Error loading AddIn " + fileName + ":\n"
+ ex.Message);
}
addIn = new AddIn();
addIn.addInFileName = fileName;
addIn.CustomErrorMessage = ex.Message;
}
if (addIn.Action == AddInAction.CustomError) {
list.Add(addIn);
continue;
}
addIn.Enabled = true;
if (disabledAddIns != null && disabledAddIns.Count > 0) {
foreach (string name in addIn.Manifest.Identities.Keys) {
if (disabledAddIns.Contains(name)) {
addIn.Enabled = false;
break;
}
}
}
if (addIn.Enabled) {
foreach (KeyValuePair pair in addIn.Manifest.Identities) {
if (dict.ContainsKey(pair.Key)) {
MessageService.ShowError("Name '" + pair.Key + "' is used by " +
"'" + addInDict[pair.Key].FileName + "' and '" + fileName + "'");
addIn.Enabled = false;
addIn.Action = AddInAction.InstalledTwice;
break;
} else {
dict.Add(pair.Key, pair.Value);
addInDict.Add(pair.Key, addIn);
}
}
}
list.Add(addIn);
}
checkDependencies:
for (int i = 0; i < list.Count; i++) {
AddIn addIn = list[i];
if (!addIn.Enabled) continue;
Version versionFound;
foreach (AddInReference reference in addIn.Manifest.Conflicts) {
if (reference.Check(dict, out versionFound)) {
MessageService.ShowError(addIn.Name + " conflicts with " + reference.ToString()
+ " and has been disabled.");
DisableAddin(addIn, dict, addInDict);
goto checkDependencies; // after removing one addin, others could break
}
}
foreach (AddInReference reference in addIn.Manifest.Dependencies) {
if (!reference.Check(dict, out versionFound)) {
if (versionFound != null) {
MessageService.ShowError(addIn.Name + " has not been loaded because it requires "
+ reference.ToString() + ", but version "
+ versionFound.ToString() + " is installed.");
} else {
MessageService.ShowError(addIn.Name + " has not been loaded because it requires "
+ reference.ToString() + ".");
}
DisableAddin(addIn, dict, addInDict);
goto checkDependencies; // after removing one addin, others could break
}
}
}
foreach (AddIn addIn in list) {
try {
InsertAddIn(addIn);
} catch (AddInLoadException ex) {
LoggingService.Error(ex);
MessageService.ShowError("Error loading AddIn " + addIn.FileName + ":\n"
+ ex.Message);
}
}
}
}
}