Free cookie consent management tool by TermsFeed Policy Generator

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

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

Do not manually include mscorlib.dll in default assemblies as it is automatically included and would cause a duplicate (#1363)

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