Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 2668 was 2668, checked in by epitzer, 14 years ago

Group assemblies by plugin and replace assembly and namespace list boxes with tree views (#842)

File size: 15.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    public readonly Dictionary<string, List<Assembly>> Plugins;
69
70    protected Dictionary<Assembly, bool> Assemblies;
71    public IEnumerable<Assembly> AvailableAssemblies {
72      get { return Assemblies.Keys; }
73    }
74
75    public IEnumerable<Assembly> SelectedAssemblies {
76      get { return Assemblies.Where(kvp => kvp.Value).Select(kvp => kvp.Key); }
77    }
78
79    private HashSet<string> namespaces;
80    public IEnumerable<string> Namespaces {
81      get { return namespaces; }
82    }
83
84    #endregion
85
86    #region Extended Accessors
87
88    public void SelectAssembly(Assembly a) {
89      if (a != null && Assemblies.ContainsKey(a))
90        Assemblies[a] = true;
91    }
92
93    public void UnselectAssembly(Assembly a) {
94      if (a != null && Assemblies.ContainsKey(a))
95        Assemblies[a] = false;
96    }
97
98    public void SelectNamespace(string ns) {
99      namespaces.Add(ns);
100    }
101
102    public void UnselectNamespace(string ns) {
103      namespaces.Remove(ns);
104    }
105
106    public void SetDescription(string description) {
107      if (description == null)
108        throw new NullReferenceException("description must not be null");
109
110      if (description != this.description) {
111        this.description = description;
112        OnDescriptionChanged();
113      }
114    }
115
116    public IEnumerable<string> GetAllNamespaces(bool selectedAssembliesOnly) {
117      var namespaces = new HashSet<string>();
118      foreach (var a in Assemblies) {
119        if (!selectedAssembliesOnly || a.Value) {
120          foreach (var t in a.Key.GetTypes()) {
121            if (t.IsPublic) {
122              namespaces.Add(t.Namespace);
123            }
124          }
125        }
126      }
127      return namespaces;
128    }
129    #endregion
130
131    #region Construction & Initialization
132
133    public ProgrammableOperator() {
134      code = "";
135      description = "An operator that can be programmed for arbitrary needs.";
136      executeMethod = null;
137      Assemblies = DiscoverAssemblies();
138      namespaces = new HashSet<string>(DiscoverNamespaces());
139      Plugins = GroupAssemblies();
140    }
141
142    private Dictionary<string, List<Assembly>> GroupAssemblies() {
143      var plugins = new Dictionary<string, List<Assembly>>();
144      var assemblyNames = Assemblies.ToDictionary(a => a.Key.Location, a => a.Key);
145      foreach (var plugin in ApplicationManager.Manager.Plugins) {
146        var aList = new List<Assembly>();
147        foreach (var aName in plugin.Assemblies) {
148          Assembly a;
149          assemblyNames.TryGetValue(aName, out a);
150          if (a != null) {
151            aList.Add(a);
152            assemblyNames.Remove(aName);
153          }
154        }
155        plugins[plugin.Name] = aList;
156      }
157      plugins["other"] = assemblyNames.Values.ToList();
158      return plugins;
159    }
160
161    protected static List<Assembly> defaultAssemblies = new List<Assembly>() {     
162      typeof(System.Linq.Enumerable).Assembly,  // add reference to version 3.5 of System.dll
163      typeof(System.Collections.Generic.List<>).Assembly,
164      typeof(System.Text.StringBuilder).Assembly,     
165      typeof(System.Data.Linq.DataContext).Assembly,
166      typeof(HeuristicLab.Core.OperatorBase).Assembly,
167      typeof(HeuristicLab.Data.IntData).Assembly,     
168     
169    };
170
171    protected static Dictionary<Assembly, bool> DiscoverAssemblies() {
172      var assemblies = new Dictionary<Assembly, bool>();
173      foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
174        try {
175          string location = a.Location;
176          assemblies.Add(a, false);
177        } catch (NotSupportedException) {
178          // NotSupportedException is thrown while accessing
179          // the Location property of the anonymously hosted
180          // dynamic methods assembly, which is related to
181          // LINQ queries
182        }
183      }
184      foreach (var a in defaultAssemblies) {
185        if (assemblies.ContainsKey(a)) {
186          assemblies[a] = true;
187        } else {
188          assemblies.Add(a, true);
189        }
190      }
191      return assemblies;
192    }
193
194    protected static List<string> DiscoverNamespaces() {
195      return new List<string>() {
196        "System",
197        "System.Collections.Generic",
198        "System.Text",
199        "System.Linq",
200        "System.Data.Linq",
201        "HeuristicLab.Core",
202        "HeuristicLab.Data",
203      };
204    }
205
206    #endregion
207
208    #region Compilation
209
210    private static CSharpCodeProvider codeProvider =
211      new CSharpCodeProvider(
212        new Dictionary<string, string>() {
213          { "CompilerVersion", "v3.5" },  // support C# 3.0 syntax
214        });
215
216    private CompilerResults DoCompile() {
217      CompilerParameters parameters = new CompilerParameters();
218      parameters.GenerateExecutable = false;
219      parameters.GenerateInMemory = true;
220      parameters.IncludeDebugInformation = false;
221      parameters.ReferencedAssemblies.AddRange(SelectedAssemblies.Select(a => a.Location).ToArray());
222      var unit = CreateCompilationUnit();
223      var writer = new StringWriter();
224      codeProvider.GenerateCodeFromCompileUnit(
225        unit,
226        writer,
227        new CodeGeneratorOptions() {
228          BracingStyle = "C",
229          ElseOnClosing = true,
230          IndentString = "  ",
231        });
232      CompilationUnitCode = writer.ToString();
233      return codeProvider.CompileAssemblyFromDom(parameters, unit);
234    }
235
236    public virtual void Compile() {
237      var results = DoCompile();
238      executeMethod = null;
239      if (results.Errors.HasErrors) {
240        CompileErrors = results.Errors;
241        StringBuilder sb = new StringBuilder();
242        foreach (CompilerError error in results.Errors) {
243          sb.Append(error.Line).Append(':')
244            .Append(error.Column).Append(": ")
245            .AppendLine(error.ErrorText);
246        }
247        throw new Exception(string.Format(
248          "Compilation of \"{0}\" failed:{1}{2}",
249          Name, Environment.NewLine,
250          sb.ToString()));
251      } else {
252        CompileErrors = null;
253        Assembly assembly = results.CompiledAssembly;
254        Type[] types = assembly.GetTypes();
255        executeMethod = types[0].GetMethod("Execute");
256      }
257    }
258
259    private CodeCompileUnit CreateCompilationUnit() {
260      CodeNamespace ns = new CodeNamespace("HeuristicLab.Operators.Programmable.CustomOperators");
261      ns.Types.Add(CreateType());
262      ns.Imports.AddRange(
263        GetSelectedAndValidNamespaces()
264        .Select(n => new CodeNamespaceImport(n))
265        .ToArray());
266      CodeCompileUnit unit = new CodeCompileUnit();
267      unit.Namespaces.Add(ns);
268      return unit;
269    }
270
271    public IEnumerable<string> GetSelectedAndValidNamespaces() {
272      var possibleNamespaces = new HashSet<string>(GetAllNamespaces(true));
273      foreach (var ns in Namespaces)
274        if (possibleNamespaces.Contains(ns))
275          yield return ns;
276    }
277
278    public static readonly Regex SafeTypeNameCharRegex = new Regex("[_a-zA-Z0-9]+");
279    public static readonly Regex SafeTypeNameRegex = new Regex("[_a-zA-Z][_a-zA-Z0-9]*");
280
281    public string CompiledTypeName {
282      get {
283        var sb = new StringBuilder();
284        foreach (string s in SafeTypeNameCharRegex.Matches(Name).Cast<Match>().Select(m => m.Value)) {
285          sb.Append(s);
286        }
287        return SafeTypeNameRegex.Match(sb.ToString()).Value;
288      }
289    }
290
291    private CodeTypeDeclaration CreateType() {
292      CodeTypeDeclaration typeDecl = new CodeTypeDeclaration(CompiledTypeName) {
293        IsClass = true,
294        TypeAttributes = TypeAttributes.Public,
295      };
296      typeDecl.Members.Add(CreateMethod());
297      return typeDecl;
298    }
299
300    public string Signature {
301      get {
302        var sb = new StringBuilder()
303        .Append("public static IOperation Execute(IOperator op, IScope scope");
304        foreach (var info in VariableInfos)
305          sb.Append(String.Format(", {0} {1}", info.DataType.Name, info.FormalName));
306        return sb.Append(")").ToString();
307      }
308    }
309
310    private static Regex lineSplitter = new Regex(@"\r\n|\r|\n");
311
312    private CodeMemberMethod CreateMethod() {
313      CodeMemberMethod method = new CodeMemberMethod();
314      method.Name = "Execute";
315      method.ReturnType = new CodeTypeReference(typeof(HeuristicLab.Core.IOperation));
316      method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
317      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IOperator), "op"));
318      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IScope), "scope"));
319      foreach (IVariableInfo info in VariableInfos)
320        method.Parameters.Add(new CodeParameterDeclarationExpression(info.DataType, info.FormalName));
321      string[] codeLines = lineSplitter.Split(code);
322      for (int i = 0; i < codeLines.Length; i++) {
323        codeLines[i] = string.Format("#line {0} \"ProgrammableOperator\"{1}{2}", i + 1, "\r\n", codeLines[i]);
324      }
325      method.Statements.Add(new CodeSnippetStatement(
326        string.Join("\r\n", codeLines) +
327        "\r\nreturn null;"));
328      return method;
329    }
330
331    #endregion
332
333    #region HeuristicLab interfaces
334
335    public override IOperation Apply(IScope scope) {
336      lock (syncRoot) {
337        if (executeMethod == null) {
338          Compile();
339        }
340      }
341
342      var parameters = new List<object>() { this, scope };
343      parameters.AddRange(VariableInfos.Select(info => GetParameter(info, scope)));
344      return (IOperation)executeMethod.Invoke(null, parameters.ToArray());
345    }
346
347    private object GetParameter(IVariableInfo info, IScope scope) {
348      if ((info.Kind & VariableKind.New) != VariableKind.New) {
349        return GetVariableValue(info.FormalName, scope, true);
350      } else {
351        var parameter = GetVariableValue(info.FormalName, scope, false, false);
352        if (parameter != null)
353          return parameter;
354        IItem value = (IItem)Activator.CreateInstance(info.DataType);
355        if (info.Local) {
356          AddVariable(new Variable(info.ActualName, value));
357        } else {
358          scope.AddVariable(new Variable(scope.TranslateName(info.FormalName), value));
359        }
360        return value;
361      }
362    }
363
364    public override IView CreateView() {
365      return new ProgrammableOperatorView(this);
366    }
367
368    public event EventHandler DescriptionChanged;
369    protected virtual void OnDescriptionChanged() {
370      if (DescriptionChanged != null)
371        DescriptionChanged(this, new EventArgs());
372    }
373    public event EventHandler CodeChanged;
374    protected virtual void OnCodeChanged() {
375      if (CodeChanged != null)
376        CodeChanged(this, new EventArgs());
377    }
378
379    #endregion
380
381    #region Persistence & Cloning
382
383    public override object Clone(IDictionary<Guid, object> clonedObjects) {
384      ProgrammableOperator clone = (ProgrammableOperator)base.Clone(clonedObjects);
385      clone.description = Description;
386      clone.code = Code;
387      clone.executeMethod = executeMethod;
388      clone.Assemblies = Assemblies.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
389      clone.namespaces = namespaces;
390      clone.CompilationUnitCode = CompilationUnitCode;
391      clone.CompileErrors = CompileErrors;
392      return clone;
393    }
394
395    public override XmlNode GetXmlNode(string name, XmlDocument document, IDictionary<Guid, IStorable> persistedObjects) {
396      XmlNode node = base.GetXmlNode(name, document, persistedObjects);
397
398      XmlNode descriptionNode = document.CreateNode(XmlNodeType.Element, "Description", null);
399      descriptionNode.InnerText = description;
400      node.AppendChild(descriptionNode);
401
402      XmlNode codeNode = document.CreateNode(XmlNodeType.Element, "Code", null);
403      codeNode.InnerText = code;
404      node.AppendChild(codeNode);
405
406      XmlNode assembliesNode = document.CreateNode(XmlNodeType.Element, "Assemblies", null);
407      foreach (var a in SelectedAssemblies) {
408        var assemblyNode = document.CreateNode(XmlNodeType.Element, "Assembly", null);
409        assemblyNode.InnerText = a.FullName;
410        assembliesNode.AppendChild(assemblyNode);
411      }
412      node.AppendChild(assembliesNode);
413
414      XmlNode namespacesNode = document.CreateNode(XmlNodeType.Element, "Namespaces", null);
415      foreach (string ns in namespaces) {
416        var nsNode = document.CreateNode(XmlNodeType.Element, "Namespace", null);
417        nsNode.InnerText = ns;
418        namespacesNode.AppendChild(nsNode);
419      }
420      node.AppendChild(namespacesNode);
421
422      return node;
423    }
424    public override void Populate(XmlNode node, IDictionary<Guid, IStorable> restoredObjects) {
425      base.Populate(node, restoredObjects);
426
427      XmlNode descriptionNode = node.SelectSingleNode("Description");
428      description = descriptionNode.InnerText;
429
430      XmlNode codeNode = node.SelectSingleNode("Code");
431      code = codeNode.InnerText;
432
433      XmlNode assembliesNode = node.SelectSingleNode("Assemblies");
434      if (assembliesNode != null) {
435        var selectedAssemblyNames = new HashSet<string>();
436        foreach (XmlNode assemblyNode in assembliesNode.ChildNodes) {
437          selectedAssemblyNames.Add(assemblyNode.InnerText);
438        }
439        var selectedAssemblies = new List<Assembly>();
440        foreach (var a in Assemblies.Keys.ToList()) {
441          Assemblies[a] = selectedAssemblyNames.Contains(a.FullName);
442        }
443      }
444      XmlNode namespacesNode = node.SelectSingleNode("Namespaces");
445      if (namespacesNode != null) {
446        namespaces.Clear();
447        var possibleNamespaces = new HashSet<string>(GetAllNamespaces(true));
448        foreach (XmlNode nsNode in namespacesNode.ChildNodes) {
449          if (possibleNamespaces.Contains(nsNode.InnerText))
450            SelectNamespace(nsNode.InnerText);
451        }
452      }
453    }
454
455    #endregion
456  }
457}
Note: See TracBrowser for help on using the repository browser.