Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 4068 was 4068, checked in by swagner, 14 years ago

Sorted usings and removed unused usings in entire solution (#1094)

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