Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Operators.Programmable/3.3/ProgrammableOperator.cs @ 3234

Last change on this file since 3234 was 3234, checked in by svonolfe, 13 years ago

Added check to ProgrammableOperator if the ApplicationManager exists so it can be used outside of HL (#842)

File size: 14.3 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 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;
38using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
39using HeuristicLab.Collections;
40
41namespace HeuristicLab.Operators.Programmable {
42
43  [Item("ProgrammableOperator", "An operator that can be programmed for arbitrary needs.")]
44  [StorableClass] 
45  public class ProgrammableOperator : Operator, IParameterizedNamedItem {
46
47    #region Fields & Properties
48
49    public new ParameterCollection Parameters {
50      get { return base.Parameters; }
51    }
52
53    private MethodInfo executeMethod;
54    public CompilerErrorCollection CompileErrors { get; private set; }
55    public string CompilationUnitCode { get; private set; }
56
57    [Storable]
58    private string code;
59    public string Code {
60      get { return code; }
61      set {
62        if (value != code) {
63          code = value;
64          executeMethod = null;
65          OnCodeChanged();
66        }
67      }
68    }
69
70    private object syncRoot = new object();
71
72    private static object initLock = new object();
73    private static Dictionary<string, List<Assembly>> defaultPluginDict;
74    private static Dictionary<Assembly, bool> defaultAssemblyDict;
75
76    public readonly Dictionary<string, List<Assembly>> Plugins;
77
78    protected Dictionary<Assembly, bool> Assemblies;
79
80    [Storable]
81    private List<string> _persistedAssemblyNames {
82      get {
83        return Assemblies.Keys.Select(a => a.FullName).ToList();
84      }
85      set {
86        var selectedAssemblyNames = new HashSet<string>(value);
87        foreach (var a in Assemblies.Keys.ToList()) {
88          Assemblies[a] = selectedAssemblyNames.Contains(a.FullName);
89        }
90      }
91    }
92
93    public IEnumerable<Assembly> AvailableAssemblies {
94      get { return Assemblies.Keys; }
95    }
96
97    public IEnumerable<Assembly> SelectedAssemblies {
98      get { return Assemblies.Where(kvp => kvp.Value).Select(kvp => kvp.Key); }
99    }
100
101    [Storable]
102    private HashSet<string> namespaces;
103    public IEnumerable<string> Namespaces {
104      get { return namespaces; }
105    }
106
107    public override bool CanChangeDescription {
108      get { return true; }
109    }
110
111    #endregion
112
113    #region Extended Accessors
114
115    public void SelectAssembly(Assembly a) {
116      if (a != null && Assemblies.ContainsKey(a))
117        Assemblies[a] = true;
118    }
119
120    public void UnselectAssembly(Assembly a) {
121      if (a != null && Assemblies.ContainsKey(a))
122        Assemblies[a] = false;
123    }
124
125    public void SelectNamespace(string ns) {
126      namespaces.Add(ns);
127    }
128
129    public void UnselectNamespace(string ns) {
130      namespaces.Remove(ns);
131    }
132
133    public void SetDescription(string description) {
134      if (description == null)
135        throw new NullReferenceException("description must not be null");
136      Description = description;
137    }
138
139    public IEnumerable<string> GetAllNamespaces(bool selectedAssembliesOnly) {
140      var namespaces = new HashSet<string>();
141      foreach (var a in Assemblies) {
142        if (!selectedAssembliesOnly || a.Value) {
143          foreach (var t in a.Key.GetTypes()) {
144            if (t.IsPublic) {
145              foreach (string ns in GetNamespaceHierachy(t.Namespace)) {
146                namespaces.Add(ns);
147              }
148            }
149          }
150        }
151      }
152      return namespaces;
153    }
154
155    private IEnumerable<string> GetNamespaceHierachy(string ns) {
156      for (int i = ns.Length; i != -1; i = ns.LastIndexOf('.', i - 1)) {
157        yield return ns.Substring(0, i);
158      }
159    }
160
161    #endregion
162
163    #region Construction & Initialization
164
165    public ProgrammableOperator() {
166      code = "";
167      executeMethod = null;
168      ProgrammableOperator.StaticInitialize();
169      Assemblies = defaultAssemblyDict;
170      Plugins = defaultPluginDict;
171      namespaces = new HashSet<string>(DiscoverNamespaces());
172      RegisterEvents();
173    }
174
175    [StorableHook(HookType.AfterDeserialization)]
176    private void RegisterEvents() {
177      Parameters.ItemsAdded += OnSignatureChanged;
178      Parameters.ItemsRemoved += OnSignatureChanged;
179      Parameters.ItemsReplaced += OnSignatureChanged;
180      Parameters.CollectionReset += OnSignatureChanged;
181    }
182
183    protected void OnSignatureChanged(object sender, CollectionItemsChangedEventArgs<IParameter> args) {
184      if (SignatureChanged != null)
185        SignatureChanged(sender, EventArgs.Empty);
186    }
187
188    private static void StaticInitialize() {
189      lock (initLock) {
190        if (defaultPluginDict != null || defaultAssemblyDict != null) return;
191        defaultAssemblyDict = DiscoverAssemblies();
192        defaultPluginDict = GroupAssemblies(defaultAssemblyDict.Keys);
193      }
194    }
195
196    private static Dictionary<string, List<Assembly>> GroupAssemblies(IEnumerable<Assembly> assemblies) {
197      var plugins = new Dictionary<string, List<Assembly>>();
198      var locationTable = assemblies.ToDictionary(a => a.Location, a => a);
199
200      if (ApplicationManager.Manager != null) {
201        foreach (var plugin in ApplicationManager.Manager.Plugins) {
202          var aList = new List<Assembly>();
203          foreach (var aName in from file in plugin.Files
204                                where file.Type == PluginFileType.Assembly
205                                select file.Name) {
206            Assembly a;
207            locationTable.TryGetValue(aName, out a);
208            if (a != null) {
209              aList.Add(a);
210              locationTable.Remove(aName);
211            }
212          }
213          plugins[plugin.Name] = aList;
214        }
215      }
216
217      plugins["other"] = locationTable.Values.ToList();
218      return plugins;
219    }
220
221    protected static List<Assembly> defaultAssemblies = new List<Assembly>() {
222      typeof(System.Linq.Enumerable).Assembly,  // add reference to version 3.5 of System.dll
223      typeof(System.Collections.Generic.List<>).Assembly,
224      typeof(System.Text.StringBuilder).Assembly,
225      typeof(System.Data.Linq.DataContext).Assembly,
226      typeof(HeuristicLab.Core.Item).Assembly,
227      typeof(HeuristicLab.Data.IntValue).Assembly,
228    };
229
230    protected static Dictionary<Assembly, bool> DiscoverAssemblies() {
231      var assemblies = new Dictionary<Assembly, bool>();
232      foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) {
233        try {
234          if (File.Exists(a.Location)) {
235            assemblies.Add(a, false);
236          }
237        } catch (NotSupportedException) {
238          // NotSupportedException is thrown while accessing
239          // the Location property of the anonymously hosted
240          // dynamic methods assembly, which is related to
241          // LINQ queries
242        }
243      }
244      foreach (var a in defaultAssemblies) {
245        if (assemblies.ContainsKey(a)) {
246          assemblies[a] = true;
247        } else {
248          assemblies.Add(a, true);
249        }
250      }
251      return assemblies;
252    }
253
254    protected static List<string> DiscoverNamespaces() {
255      return new List<string>() {
256        "System",
257        "System.Collections.Generic",
258        "System.Text",
259        "System.Linq",
260        "System.Data.Linq",
261        "HeuristicLab.Core",
262        "HeuristicLab.Data",
263      };
264    }
265
266    #endregion
267
268    #region Compilation
269
270    private static CSharpCodeProvider codeProvider =
271      new CSharpCodeProvider(
272        new Dictionary<string, string>() {
273          { "CompilerVersion", "v3.5" },  // support C# 3.0 syntax
274        });
275
276    private CompilerResults DoCompile() {
277      CompilerParameters parameters = new CompilerParameters();
278      parameters.GenerateExecutable = false;
279      parameters.GenerateInMemory = true;
280      parameters.IncludeDebugInformation = false;
281      parameters.ReferencedAssemblies.AddRange(SelectedAssemblies.Select(a => a.Location).ToArray());
282      var unit = CreateCompilationUnit();
283      var writer = new StringWriter();
284      codeProvider.GenerateCodeFromCompileUnit(
285        unit,
286        writer,
287        new CodeGeneratorOptions() {
288          BracingStyle = "C",
289          ElseOnClosing = true,
290          IndentString = "  ",
291        });
292      CompilationUnitCode = writer.ToString();
293      return codeProvider.CompileAssemblyFromDom(parameters, unit);
294    }
295
296    public virtual void Compile() {
297      var results = DoCompile();
298      executeMethod = null;
299      if (results.Errors.HasErrors) {
300        CompileErrors = results.Errors;
301        StringBuilder sb = new StringBuilder();
302        foreach (CompilerError error in results.Errors) {
303          sb.Append(error.Line).Append(':')
304            .Append(error.Column).Append(": ")
305            .AppendLine(error.ErrorText);
306        }
307        throw new Exception(string.Format(
308          "Compilation of \"{0}\" failed:{1}{2}",
309          Name, Environment.NewLine,
310          sb.ToString()));
311      } else {
312        CompileErrors = null;
313        Assembly assembly = results.CompiledAssembly;
314        Type[] types = assembly.GetTypes();
315        executeMethod = types[0].GetMethod("Execute");
316      }
317    }
318
319    private CodeCompileUnit CreateCompilationUnit() {
320      CodeNamespace ns = new CodeNamespace("HeuristicLab.Operators.Programmable.CustomOperators");
321      ns.Types.Add(CreateType());
322      ns.Imports.AddRange(
323        GetSelectedAndValidNamespaces()
324        .Select(n => new CodeNamespaceImport(n))
325        .ToArray());
326      CodeCompileUnit unit = new CodeCompileUnit();
327      unit.Namespaces.Add(ns);
328      return unit;
329    }
330
331    public IEnumerable<string> GetSelectedAndValidNamespaces() {
332      var possibleNamespaces = new HashSet<string>(GetAllNamespaces(true));
333      foreach (var ns in Namespaces)
334        if (possibleNamespaces.Contains(ns))
335          yield return ns;
336    }
337
338    public static readonly Regex SafeTypeNameCharRegex = new Regex("[_a-zA-Z0-9]+");
339    public static readonly Regex SafeTypeNameRegex = new Regex("[_a-zA-Z][_a-zA-Z0-9]*");
340
341    public string CompiledTypeName {
342      get {
343        var sb = new StringBuilder();
344        foreach (string s in SafeTypeNameCharRegex.Matches(Name).Cast<Match>().Select(m => m.Value)) {
345          sb.Append(s);
346        }
347        return SafeTypeNameRegex.Match(sb.ToString()).Value;
348      }
349    }
350
351    private CodeTypeDeclaration CreateType() {
352      CodeTypeDeclaration typeDecl = new CodeTypeDeclaration(CompiledTypeName) {
353        IsClass = true,
354        TypeAttributes = TypeAttributes.Public,
355      };
356      typeDecl.Members.Add(CreateMethod());
357      return typeDecl;
358    }
359
360    public string Signature {
361      get {
362        var sb = new StringBuilder()
363        .Append("public static IOperation Execute(IOperator op, IExecutionContext context");
364        foreach (IParameter param in Parameters) {
365          sb.Append(String.Format(", {0} {1}", param.DataType.Name, param.Name));
366        }
367        return sb.Append(")").ToString();
368      }
369    }
370
371    public event EventHandler SignatureChanged;
372
373    private static Regex lineSplitter = new Regex(@"\r\n|\r|\n");
374
375    private CodeMemberMethod CreateMethod() {
376      CodeMemberMethod method = new CodeMemberMethod();
377      method.Name = "Execute";
378      method.ReturnType = new CodeTypeReference(typeof(IOperation));
379      method.Attributes = MemberAttributes.Public | MemberAttributes.Static;
380      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IOperator), "op"));
381      method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IExecutionContext), "context"));
382      foreach (var param in Parameters)
383        method.Parameters.Add(new CodeParameterDeclarationExpression(param.DataType, param.Name));
384      string[] codeLines = lineSplitter.Split(code);
385      for (int i = 0; i < codeLines.Length; i++) {
386        codeLines[i] = string.Format("#line {0} \"ProgrammableOperator\"{1}{2}", i + 1, "\r\n", codeLines[i]);
387      }
388      method.Statements.Add(new CodeSnippetStatement(
389        string.Join("\r\n", codeLines) +
390        "\r\nreturn null;"));
391      return method;
392    }
393
394    #endregion
395
396    #region HeuristicLab interfaces
397
398    public override IOperation Apply() {
399      lock (syncRoot) {
400        if (executeMethod == null) {
401          Compile();
402        }
403      }
404
405      var parameters = new List<object>() { this, ExecutionContext };
406      parameters.AddRange(Parameters.Select(p => (object)p.ActualValue));
407      return (IOperation)executeMethod.Invoke(null, parameters.ToArray());
408    }
409
410    public event EventHandler CodeChanged;
411    protected virtual void OnCodeChanged() {
412      if (CodeChanged != null)
413        CodeChanged(this, new EventArgs());
414    }
415
416    #endregion
417
418    #region Cloning
419
420    public override IDeepCloneable Clone(Cloner cloner) {
421      ProgrammableOperator clone = (ProgrammableOperator)base.Clone(cloner);
422      clone.Description = Description;
423      clone.code = Code;
424      clone.executeMethod = executeMethod;
425      clone.Assemblies = Assemblies.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
426      clone.namespaces = namespaces;
427      clone.CompilationUnitCode = CompilationUnitCode;
428      clone.CompileErrors = CompileErrors;
429      clone.RegisterEvents();
430      return clone;
431    }
432
433    #endregion
434
435  }
436}
Note: See TracBrowser for help on using the repository browser.