Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 2803 was 2799, checked in by epitzer, 15 years ago

towards migration of ProgrammableOperator to HL 3.3 (#842)

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