Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Operators.Programmable/3.2/ProgrammableOperator.cs @ 2978

Last change on this file since 2978 was 2688, checked in by gkronber, 14 years ago

Implemented an enumerable to iterate through all PluginFiles as suggested by swagner, replaced the Assemblies enumerable with an AssemblyName enumerable for internal usage in the plugin infrastructure and replaced Assembly.LoadFrom calls with Assembly.Load() to prevent loading from GAC as far as possible.

#850 (PluginInfrastructure should provide a way to get assemblies associated with a plug-in)

File size: 16.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2008 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.Xml;
26using System.IO;
27using System.Linq;
28using System.Reflection;
29using System.CodeDom;
30using System.CodeDom.Compiler;
31using Microsoft.CSharp;
32using System.Text.RegularExpressions;
33using HeuristicLab.Core;
34using HeuristicLab.Data;
35using System.Data.Linq;
36using System.Xml.XPath;
37using HeuristicLab.PluginInfrastructure;
38
39namespace HeuristicLab.Operators.Programmable {
40
41  public class ProgrammableOperator : OperatorBase {
42
43    #region Fields & Properties
44
45    private MethodInfo executeMethod;
46    public CompilerErrorCollection CompileErrors { get; private set; }
47    public string CompilationUnitCode { get; private set; }
48
49    private string description;
50    public override string Description {
51      get { return description; }
52    }
53
54    private string code;
55    public string Code {
56      get { return code; }
57      set {
58        if (value != code) {
59          code = value;
60          executeMethod = null;
61          OnCodeChanged();
62        }
63      }
64    }
65
66    private object syncRoot = new object();
67
68    private static object initLock = new object();
69    private static Dictionary<string, List<Assembly>> defaultPluginDict;
70    private static Dictionary<Assembly, bool> defaultAssemblyDict;
71
72    public readonly Dictionary<string, List<Assembly>> Plugins;
73
74    protected Dictionary<Assembly, bool> Assemblies;
75    public IEnumerable<Assembly> AvailableAssemblies {
76      get { return Assemblies.Keys; }
77    }
78
79    public IEnumerable<Assembly> SelectedAssemblies {
80      get { return Assemblies.Where(kvp => kvp.Value).Select(kvp => kvp.Key); }
81    }
82
83    private HashSet<string> namespaces;
84    public IEnumerable<string> Namespaces {
85      get { return namespaces; }
86    }
87
88    #endregion
89
90    #region Extended Accessors
91
92    public void SelectAssembly(Assembly a) {
93      if (a != null && Assemblies.ContainsKey(a))
94        Assemblies[a] = true;
95    }
96
97    public void UnselectAssembly(Assembly a) {
98      if (a != null && Assemblies.ContainsKey(a))
99        Assemblies[a] = false;
100    }
101
102    public void SelectNamespace(string ns) {
103      namespaces.Add(ns);
104    }
105
106    public void UnselectNamespace(string ns) {
107      namespaces.Remove(ns);
108    }
109
110    public void SetDescription(string description) {
111      if (description == null)
112        throw new NullReferenceException("description must not be null");
113
114      if (description != this.description) {
115        this.description = description;
116        OnDescriptionChanged();
117      }
118    }
119
120    public IEnumerable<string> GetAllNamespaces(bool selectedAssembliesOnly) {
121      var namespaces = new HashSet<string>();
122      foreach (var a in Assemblies) {
123        if (!selectedAssembliesOnly || a.Value) {
124          foreach (var t in a.Key.GetTypes()) {
125            if (t.IsPublic) {
126              foreach (string ns in GetNamespaceHierachy(t.Namespace)) {
127                namespaces.Add(ns);
128              }
129            }
130          }
131        }
132      }
133      return namespaces;
134    }
135
136    private IEnumerable<string> GetNamespaceHierachy(string ns) {
137      for (int i = ns.Length; i != -1; i = ns.LastIndexOf('.', i - 1)) {
138        yield return ns.Substring(0, i);
139      }
140    }
141
142    #endregion
143
144    #region Construction & Initialization
145
146    public ProgrammableOperator() {
147      code = "";
148      description = "An operator that can be programmed for arbitrary needs.";
149      executeMethod = null;
150      ProgrammableOperator.StaticInitialize();
151      Assemblies = defaultAssemblyDict;
152      Plugins = defaultPluginDict;
153      namespaces = new HashSet<string>(DiscoverNamespaces());
154    }
155
156    private static void StaticInitialize() {
157      lock (initLock) {
158        if (defaultPluginDict != null || defaultAssemblyDict != null) return;
159        defaultAssemblyDict = DiscoverAssemblies();
160        defaultPluginDict = GroupAssemblies(defaultAssemblyDict.Keys);
161      }
162    }
163
164    private static Dictionary<string, List<Assembly>> GroupAssemblies(IEnumerable<Assembly> assemblies) {
165      var plugins = new Dictionary<string, List<Assembly>>();
166      var locationTable = assemblies.ToDictionary(a => a.Location, a => a);
167      foreach (var plugin in ApplicationManager.Manager.Plugins) {
168        var aList = new List<Assembly>();
169        foreach (var aName in from file in plugin.Files
170                              where file.Type == PluginFileType.Assembly
171                              select file.Name) {
172          Assembly a;
173          locationTable.TryGetValue(aName, out a);
174          if (a != null) {
175            aList.Add(a);
176            locationTable.Remove(aName);
177          }
178        }
179        plugins[plugin.Name] = aList;
180      }
181      plugins["other"] = locationTable.Values.ToList();
182      return plugins;
183    }
184
185    protected static List<Assembly> defaultAssemblies = new List<Assembly>() {     
186      typeof(System.Linq.Enumerable).Assembly,  // add reference to version 3.5 of System.dll
187      typeof(System.Collections.Generic.List<>).Assembly,
188      typeof(System.Text.StringBuilder).Assembly,     
189      typeof(System.Data.Linq.DataContext).Assembly,
190      typeof(HeuristicLab.Core.OperatorBase).Assembly,
191      typeof(HeuristicLab.Data.IntData).Assembly,     
192     
193    };
194
195    protected static Dictionary<Assembly, bool> DiscoverAssemblies() {
196      var assemblies = new Dictionary<Assembly, bool>();
197      foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
198        try {
199          if (File.Exists(a.Location)) {
200            assemblies.Add(a, false);
201          }
202        }
203        catch (NotSupportedException) {
204          // NotSupportedException is thrown while accessing
205          // the Location property of the anonymously hosted
206          // dynamic methods assembly, which is related to
207          // LINQ queries
208        }
209      }
210      foreach (var a in defaultAssemblies) {
211        if (assemblies.ContainsKey(a)) {
212          assemblies[a] = true;
213        } else {
214          assemblies.Add(a, true);
215        }
216      }
217      return assemblies;
218    }
219
220    protected static List<string> DiscoverNamespaces() {
221      return new List<string>() {
222        "System",
223        "System.Collections.Generic",
224        "System.Text",
225        "System.Linq",
226        "System.Data.Linq",
227        "HeuristicLab.Core",
228        "HeuristicLab.Data",
229      };
230    }
231
232    #endregion
233
234    #region Compilation
235
236    private static CSharpCodeProvider codeProvider =
237      new CSharpCodeProvider(
238        new Dictionary<string, string>() {
239          { "CompilerVersion", "v3.5" },  // support C# 3.0 syntax
240        });
241
242    private CompilerResults DoCompile() {
243      CompilerParameters parameters = new CompilerParameters();
244      parameters.GenerateExecutable = false;
245      parameters.GenerateInMemory = true;
246      parameters.IncludeDebugInformation = false;
247      parameters.ReferencedAssemblies.AddRange(SelectedAssemblies.Select(a => a.Location).ToArray());
248      var unit = CreateCompilationUnit();
249      var writer = new StringWriter();
250      codeProvider.GenerateCodeFromCompileUnit(
251        unit,
252        writer,
253        new CodeGeneratorOptions() {
254          BracingStyle = "C",
255          ElseOnClosing = true,
256          IndentString = "  ",
257        });
258      CompilationUnitCode = writer.ToString();
259      return codeProvider.CompileAssemblyFromDom(parameters, unit);
260    }
261
262    public virtual void Compile() {
263      var results = DoCompile();
264      executeMethod = null;
265      if (results.Errors.HasErrors) {
266        CompileErrors = results.Errors;
267        StringBuilder sb = new StringBuilder();
268        foreach (CompilerError error in results.Errors) {
269          sb.Append(error.Line).Append(':')
270            .Append(error.Column).Append(": ")
271            .AppendLine(error.ErrorText);
272        }
273        throw new Exception(string.Format(
274          "Compilation of \"{0}\" failed:{1}{2}",
275          Name, Environment.NewLine,
276          sb.ToString()));
277      } else {
278        CompileErrors = null;
279        Assembly assembly = results.CompiledAssembly;
280        Type[] types = assembly.GetTypes();
281        executeMethod = types[0].GetMethod("Execute");
282      }
283    }
284
285    private CodeCompileUnit CreateCompilationUnit() {
286      CodeNamespace ns = new CodeNamespace("HeuristicLab.Operators.Programmable.CustomOperators");
287      ns.Types.Add(CreateType());
288      ns.Imports.AddRange(
289        GetSelectedAndValidNamespaces()
290        .Select(n => new CodeNamespaceImport(n))
291        .ToArray());
292      CodeCompileUnit unit = new CodeCompileUnit();
293      unit.Namespaces.Add(ns);
294      return unit;
295    }
296
297    public IEnumerable<string> GetSelectedAndValidNamespaces() {
298      var possibleNamespaces = new HashSet<string>(GetAllNamespaces(true));
299      foreach (var ns in Namespaces)
300        if (possibleNamespaces.Contains(ns))
301          yield return ns;
302    }
303
304    public static readonly Regex SafeTypeNameCharRegex = new Regex("[_a-zA-Z0-9]+");
305    public static readonly Regex SafeTypeNameRegex = new Regex("[_a-zA-Z][_a-zA-Z0-9]*");
306
307    public string CompiledTypeName {
308      get {
309        var sb = new StringBuilder();
310        foreach (string s in SafeTypeNameCharRegex.Matches(Name).Cast<Match>().Select(m => m.Value)) {
311          sb.Append(s);
312        }
313        return SafeTypeNameRegex.Match(sb.ToString()).Value;
314      }
315    }
316
317    private CodeTypeDeclaration CreateType() {
318      CodeTypeDeclaration typeDecl = new CodeTypeDeclaration(CompiledTypeName) {
319        IsClass = true,
320        TypeAttributes = TypeAttributes.Public,
321      };
322      typeDecl.Members.Add(CreateMethod());
323      return typeDecl;
324    }
325
326    public string Signature {
327      get {
328        var sb = new StringBuilder()
329        .Append("public static IOperation Execute(IOperator op, IScope scope");
330        foreach (var info in VariableInfos)
331          sb.Append(String.Format(", {0} {1}", info.DataType.Name, info.FormalName));
332        return sb.Append(")").ToString();
333      }
334    }
335
336    private static Regex lineSplitter = new Regex(@"\r\n|\r|\n");
337
338    private CodeMemberMethod CreateMethod() {
339      CodeMemberMethod method = new CodeMemberMethod();
340      method.Name = "Execute";
341      method.ReturnType = new CodeTypeReference(typeof(HeuristicLab.Core.IOperation));
342      method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
343      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IOperator), "op"));
344      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IScope), "scope"));
345      foreach (IVariableInfo info in VariableInfos)
346        method.Parameters.Add(new CodeParameterDeclarationExpression(info.DataType, info.FormalName));
347      string[] codeLines = lineSplitter.Split(code);
348      for (int i = 0; i < codeLines.Length; i++) {
349        codeLines[i] = string.Format("#line {0} \"ProgrammableOperator\"{1}{2}", i + 1, "\r\n", codeLines[i]);
350      }
351      method.Statements.Add(new CodeSnippetStatement(
352        string.Join("\r\n", codeLines) +
353        "\r\nreturn null;"));
354      return method;
355    }
356
357    #endregion
358
359    #region HeuristicLab interfaces
360
361    public override IOperation Apply(IScope scope) {
362      lock (syncRoot) {
363        if (executeMethod == null) {
364          Compile();
365        }
366      }
367
368      var parameters = new List<object>() { this, scope };
369      parameters.AddRange(VariableInfos.Select(info => GetParameter(info, scope)));
370      return (IOperation)executeMethod.Invoke(null, parameters.ToArray());
371    }
372
373    private object GetParameter(IVariableInfo info, IScope scope) {
374      if ((info.Kind & VariableKind.New) != VariableKind.New) {
375        return GetVariableValue(info.FormalName, scope, true);
376      } else {
377        var parameter = GetVariableValue(info.FormalName, scope, false, false);
378        if (parameter != null)
379          return parameter;
380        IItem value = (IItem)Activator.CreateInstance(info.DataType);
381        if (info.Local) {
382          AddVariable(new Variable(info.ActualName, value));
383        } else {
384          scope.AddVariable(new Variable(scope.TranslateName(info.FormalName), value));
385        }
386        return value;
387      }
388    }
389
390    public override IView CreateView() {
391      return new ProgrammableOperatorView(this);
392    }
393
394    public event EventHandler DescriptionChanged;
395    protected virtual void OnDescriptionChanged() {
396      if (DescriptionChanged != null)
397        DescriptionChanged(this, new EventArgs());
398    }
399    public event EventHandler CodeChanged;
400    protected virtual void OnCodeChanged() {
401      if (CodeChanged != null)
402        CodeChanged(this, new EventArgs());
403    }
404
405    #endregion
406
407    #region Persistence & Cloning
408
409    public override object Clone(IDictionary<Guid, object> clonedObjects) {
410      ProgrammableOperator clone = (ProgrammableOperator)base.Clone(clonedObjects);
411      clone.description = Description;
412      clone.code = Code;
413      clone.executeMethod = executeMethod;
414      clone.Assemblies = Assemblies.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
415      clone.namespaces = namespaces;
416      clone.CompilationUnitCode = CompilationUnitCode;
417      clone.CompileErrors = CompileErrors;
418      return clone;
419    }
420
421    public override XmlNode GetXmlNode(string name, XmlDocument document, IDictionary<Guid, IStorable> persistedObjects) {
422      XmlNode node = base.GetXmlNode(name, document, persistedObjects);
423
424      XmlNode descriptionNode = document.CreateNode(XmlNodeType.Element, "Description", null);
425      descriptionNode.InnerText = description;
426      node.AppendChild(descriptionNode);
427
428      XmlNode codeNode = document.CreateNode(XmlNodeType.Element, "Code", null);
429      codeNode.InnerText = code;
430      node.AppendChild(codeNode);
431
432      XmlNode assembliesNode = document.CreateNode(XmlNodeType.Element, "Assemblies", null);
433      foreach (var a in SelectedAssemblies) {
434        var assemblyNode = document.CreateNode(XmlNodeType.Element, "Assembly", null);
435        assemblyNode.InnerText = a.FullName;
436        assembliesNode.AppendChild(assemblyNode);
437      }
438      node.AppendChild(assembliesNode);
439
440      XmlNode namespacesNode = document.CreateNode(XmlNodeType.Element, "Namespaces", null);
441      foreach (string ns in namespaces) {
442        var nsNode = document.CreateNode(XmlNodeType.Element, "Namespace", null);
443        nsNode.InnerText = ns;
444        namespacesNode.AppendChild(nsNode);
445      }
446      node.AppendChild(namespacesNode);
447
448      return node;
449    }
450    public override void Populate(XmlNode node, IDictionary<Guid, IStorable> restoredObjects) {
451      base.Populate(node, restoredObjects);
452
453      XmlNode descriptionNode = node.SelectSingleNode("Description");
454      description = descriptionNode.InnerText;
455
456      XmlNode codeNode = node.SelectSingleNode("Code");
457      code = codeNode.InnerText;
458
459      XmlNode assembliesNode = node.SelectSingleNode("Assemblies");
460      if (assembliesNode != null) {
461        var selectedAssemblyNames = new HashSet<string>();
462        foreach (XmlNode assemblyNode in assembliesNode.ChildNodes) {
463          selectedAssemblyNames.Add(assemblyNode.InnerText);
464        }
465        var selectedAssemblies = new List<Assembly>();
466        foreach (var a in Assemblies.Keys.ToList()) {
467          Assemblies[a] = selectedAssemblyNames.Contains(a.FullName);
468        }
469      }
470      XmlNode namespacesNode = node.SelectSingleNode("Namespaces");
471      if (namespacesNode != null) {
472        namespaces.Clear();
473        var possibleNamespaces = new HashSet<string>(GetAllNamespaces(true));
474        foreach (XmlNode nsNode in namespacesNode.ChildNodes) {
475          if (possibleNamespaces.Contains(nsNode.InnerText))
476            SelectNamespace(nsNode.InnerText);
477        }
478      }
479    }
480
481    #endregion
482  }
483}
Note: See TracBrowser for help on using the repository browser.