Java tutorial
package org.drools.compiler; /* * Copyright 2005 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.io.Reader; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.jci.compilers.CompilationResult; import org.apache.commons.jci.compilers.EclipseJavaCompiler; import org.apache.commons.jci.compilers.EclipseJavaCompilerSettings; import org.apache.commons.jci.compilers.JavaCompiler; import org.apache.commons.jci.compilers.JavaCompilerFactory; import org.apache.commons.jci.problems.CompilationProblem; import org.apache.commons.jci.readers.MemoryResourceReader; import org.apache.commons.jci.readers.ResourceReader; import org.drools.DroolsEnv; import org.drools.RuntimeDroolsException; import org.drools.base.ClassFieldExtractorCache; import org.drools.lang.descr.FunctionDescr; import org.drools.lang.descr.PackageDescr; import org.drools.lang.descr.PatternDescr; import org.drools.lang.descr.RuleDescr; import org.drools.rule.Package; import org.drools.rule.Rule; import org.drools.semantics.java.ClassTypeResolver; import org.drools.semantics.java.FunctionBuilder; import org.drools.semantics.java.PackageStore; import org.drools.semantics.java.RuleBuilder; import org.drools.spi.FieldExtractor; import org.drools.spi.TypeResolver; import org.drools.xml.XmlPackageReader; import org.xml.sax.SAXException; /** * This is the main compiler class for parsing and compiling rules and assembling or merging them into a * binary Package instance. * This can be done by merging into existing binary packages, or totally from source. */ public class PackageBuilder { private JavaCompiler compiler; private Package pkg; private List results; private PackageStore packageStoreWrapper; private MemoryResourceReader src; private PackageBuilderConfiguration configuration; private Map errorHandlers; private List generatedClassList; private ClassTypeResolver typeResolver; private ClassFieldExtractorCache classFieldExtractorCache; /** * Use this when package is starting from scratch. */ public PackageBuilder() { this(null, null); } /** * This will allow you to merge rules into this pre existing package. */ public PackageBuilder(final Package pkg) { this(pkg, null); } public PackageBuilder(PackageBuilderConfiguration configuration) { this(null, configuration); } /** * This allows you to pass in a pre existing package, and a configuration (for instance to set the classloader). * @param pkg A pre existing package (can be null if none exists) * @param configuration Optional configuration for this builder. */ public PackageBuilder(final Package pkg, PackageBuilderConfiguration configuration) { if (configuration == null) { configuration = new PackageBuilderConfiguration(); } this.configuration = configuration; loadCompiler(); this.src = new MemoryResourceReader(); this.results = new ArrayList(); this.errorHandlers = new HashMap(); this.pkg = pkg; this.generatedClassList = new ArrayList(); this.classFieldExtractorCache = new ClassFieldExtractorCache(); if (pkg != null) { this.packageStoreWrapper = new PackageStore(pkg.getPackageCompilationData()); } } /* MODIFIED BY CHINMAY RITU */ public void addPackageFromDrl(String fileName, final Reader reader) throws DroolsParserException, IOException { final DrlParser parser = new DrlParser(); final PackageDescr pkg = parser.parse(reader, fileName); this.results.addAll(parser.getErrors()); addPackage(pkg); } /** * Load a rule package from DRL source. * @param reader * @throws DroolsParserException * @throws IOException */ public void addPackageFromDrl(final Reader reader) throws DroolsParserException, IOException { addPackageFromDrl("", reader); } /** * Load a rule package from XML source. * @param reader * @throws DroolsParserException * @throws IOException */ public void addPackageFromXml(final Reader reader) throws DroolsParserException, IOException { final XmlPackageReader xmlReader = new XmlPackageReader(); try { xmlReader.read(reader); } catch (final SAXException e) { throw new DroolsParserException(e.getCause()); } addPackage(xmlReader.getPackageDescr()); } /** * Load a rule package from DRL source using the supplied DSL configuration. * @param source The source of the rules. * @param dsl The source of the domain specific language configuration. * @throws DroolsParserException * @throws IOException */ public void addPackageFromDrl(final Reader source, final Reader dsl) throws DroolsParserException, IOException { final DrlParser parser = new DrlParser(); final PackageDescr pkg = parser.parse(source, dsl); this.results.addAll(parser.getErrors()); addPackage(pkg); } /** * This adds a package from a Descr/AST * This will also trigger a compile, if there are any generated classes to compile * of course. */ public void addPackage(final PackageDescr packageDescr) { validatePackageName(packageDescr); validateUniqueRuleNames(packageDescr); if (this.pkg != null) { //mergePackage( packageDescr ) ; mergePackage(this.pkg, packageDescr); } else { this.pkg = newPackage(packageDescr); } //only try to compile if there are no parse errors if (!hasErrors()) { //iterate and compile for (final Iterator it = packageDescr.getFunctions().iterator(); it.hasNext();) { addFunction((FunctionDescr) it.next()); } //iterate and compile for (final Iterator it = packageDescr.getRules().iterator(); it.hasNext();) { addRule((RuleDescr) it.next()); } } if (this.generatedClassList.size() > 0) { this.compileAll(); } } private void validatePackageName(final PackageDescr packageDescr) { if (packageDescr.getName() == null || "".equals(packageDescr.getName())) { throw new MissingPackageNameException("Missing package name for rule package."); } } private void validateUniqueRuleNames(final PackageDescr packageDescr) { Set names = new HashSet(); for (Iterator iter = packageDescr.getRules().iterator(); iter.hasNext();) { RuleDescr rule = (RuleDescr) iter.next(); String name = rule.getName(); if (names.contains(name)) { this.results.add(new ParserError("Duplicate rule name: " + name, rule.getLine(), rule.getColumn())); } names.add(name); } } private Package newPackage(final PackageDescr packageDescr) { final Package pkg = new Package(packageDescr.getName(), this.configuration.getClassLoader()); this.packageStoreWrapper = new PackageStore(pkg.getPackageCompilationData()); mergePackage(pkg, packageDescr); return pkg; } private void mergePackage(final Package pkg, final PackageDescr packageDescr) { final List imports = packageDescr.getImports(); for (final Iterator it = imports.iterator(); it.hasNext();) { pkg.addImport((String) it.next()); } TypeResolver tResolver = null; if (DroolsEnv.isJava()) { tResolver = new ClassTypeResolver(imports, pkg.getPackageCompilationData().getClassLoader()); } else { Class cl; try { cl = Class.forName("cli.org.drools.dotnet.semantics.DotnetClassTypeResolver"); tResolver = (ClassTypeResolver) cl.getConstructors()[0] .newInstance(new Object[] { imports, pkg.getPackageCompilationData().getClassLoader() }); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } final TypeResolver typeResolver = tResolver; final Map globals = packageDescr.getGlobals(); for (final Iterator it = globals.keySet().iterator(); it.hasNext();) { final String identifier = (String) it.next(); final String className = (String) globals.get(identifier); Class clazz; try { clazz = typeResolver.resolveType(className); pkg.addGlobal(identifier, clazz); } catch (final ClassNotFoundException e) { new GlobalError(identifier); } } } /** * This adds a compile "task" for when the compiler of * semantics (JCI) is called later on with compileAll()\ * which actually does the compiling. * The ErrorHandler is required to map the errors back to the * element that caused it. */ private void addClassCompileTask(final String className, final String text, final MemoryResourceReader src, ErrorHandler handler) { String fileName = className.replace('.', '/') + ".java"; src.add(fileName, text.getBytes()); this.errorHandlers.put(fileName, handler); this.generatedClassList.add(className); } private void addFunction(final FunctionDescr functionDescr) { final FunctionBuilder buidler = new FunctionBuilder(); addClassCompileTask(this.pkg.getName() + "." + ucFirst(functionDescr.getName()), buidler.build(this.pkg, functionDescr), this.src, new FunctionErrorHandler(functionDescr, "Function Compilation error")); } private void addRule(final RuleDescr ruleDescr) { final String ruleClassName = getUniqueLegalName(this.pkg.getName(), ruleDescr.getName(), "java", this.src); ruleDescr.SetClassName(ucFirst(ruleClassName)); final RuleBuilder builder = new RuleBuilder(getTypeResolver(), classFieldExtractorCache); builder.build(this.pkg, ruleDescr); this.results.addAll(builder.getErrors()); final Rule rule = builder.getRule(); // Check if there is any code to compile. If so compile it. if (builder.getRuleClass() != null) { addRuleSemantics(builder, rule, ruleDescr); } this.pkg.addRule(rule); } /** * @return a Type resolver, lazily. * If one does not exist yet, it will be initialised. */ private TypeResolver getTypeResolver() { if (this.typeResolver == null) { if (DroolsEnv.isJava()) { typeResolver = new ClassTypeResolver(pkg.getImports(), pkg.getPackageCompilationData().getClassLoader()); // make an automatic import for the current package typeResolver.addImport(pkg.getName() + ".*"); typeResolver.addImport("java.lang.*"); } else { Class cl; try { cl = Class.forName("cli.org.drools.dotnet.semantics.DotnetClassTypeResolver"); typeResolver = (ClassTypeResolver) cl.getConstructors()[0].newInstance( new Object[] { pkg.getImports(), pkg.getPackageCompilationData().getClassLoader() }); typeResolver.addImport(pkg.getName()); typeResolver.addImport("System"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return this.typeResolver; } /** * @deprecated Do not use for compiling rules. Do a whole package at a time. */ public void compileRule(final RuleBuilder builder, final Rule rule, final RuleDescr ruleDescr) { addRuleSemantics(builder, rule, ruleDescr); this.compileAll(); } /** * This will setup the semantic components of the rule for compiling later on. * It will not actually call the compiler */ private void addRuleSemantics(final RuleBuilder builder, final Rule rule, final RuleDescr ruleDescr) { // The compilation result is for th entire rule, so difficult to associate with any descr addClassCompileTask(this.pkg.getName() + "." + ruleDescr.getClassName(), builder.getRuleClass(), this.src, new RuleErrorHandler(ruleDescr, rule, "Rule Compilation error")); for (final Iterator it = builder.getInvokers().keySet().iterator(); it.hasNext();) { final String className = (String) it.next(); // Check if an invoker - returnvalue, predicate, eval or consequence has been associated // If so we add it to the PackageCompilationData as it will get wired up on compilation final Object invoker = builder.getInvokerLookups().get(className); if (invoker != null) { this.pkg.getPackageCompilationData().putInvoker(className, invoker); } final String text = (String) builder.getInvokers().get(className); //System.out.println( className + ":\n" + text ); final PatternDescr descr = (PatternDescr) builder.getDescrLookups().get(className); addClassCompileTask(className, text, this.src, new RuleInvokerErrorHandler(descr, rule, "Unable to generate rule invoker.")); } } /** * @return The compiled package. The package may contain errors, which you can report on * by calling getErrors or printErrors. If you try to add an invalid package (or rule) * to a RuleBase, you will get a runtime exception. * * Compiled packages are serializable. */ public Package getPackage() { if (hasErrors()) { this.pkg.setError(this.printErrors()); } return this.pkg; } /** * This actually triggers the compiling of all the resources. * Errors are mapped back to the element that originally generated the semantic * code. */ private void compileAll() { String[] classes = new String[this.generatedClassList.size()]; this.generatedClassList.toArray(classes); //byte[] byteArray = src.getBytes(classes[0].replace('.', '/')+".java"); //String str = new String(byteArray); //System.out.println(str); //byte[] byteArray = src.getBytes(classes[0].replace('.','/')+".java"); //String fileContents = new String(byteArray); final CompilationResult result = this.compiler.compile(classes, src, this.packageStoreWrapper, this.pkg.getPackageCompilationData().getClassLoader()); //this will sort out the errors based on what class/file they happened in if (result.getErrors().length > 0) { for (int i = 0; i < result.getErrors().length; i++) { CompilationProblem err = result.getErrors()[i]; ErrorHandler handler = (ErrorHandler) this.errorHandlers.get(err.getFileName()); handler.addError(err); } Collection errors = this.errorHandlers.values(); for (Iterator iter = errors.iterator(); iter.hasNext();) { ErrorHandler handler = (ErrorHandler) iter.next(); if (!(handler instanceof RuleInvokerErrorHandler)) { this.results.add(handler.getError()); } else { //we don't really want to report invoker errors. //mostly as they can happen when there is a syntax error in the RHS //and otherwise, it is a programmatic error in drools itself. System.err.println("!!!! An error occurred compiling the invoker: " + handler.getError()); } } } } /** This will return true if there were errors in the package building and compiling phase */ public boolean hasErrors() { return this.results.size() > 0; } /** * @return A list of Error objects that resulted from building and compiling the package. */ public DroolsError[] getErrors() { return (DroolsError[]) this.results.toArray(new DroolsError[this.results.size()]); } /** * This will pretty print the errors (from getErrors()) * into lines. */ public String printErrors() { final StringBuffer buf = new StringBuffer(); for (final Iterator iter = this.results.iterator(); iter.hasNext();) { final DroolsError err = (DroolsError) iter.next(); buf.append(err.getMessage()); buf.append("\n"); } return buf.toString(); } /** * Takes a given name and makes sure that its legal and doesn't already exist. If the file exists it increases counter appender untill it is unique. * * @param packageName * @param name * @param ext * @return */ private String getUniqueLegalName(final String packageName, final String name, final String ext, final ResourceReader src) { // replaces all non alphanumeric or $ chars with _ String newName = "Rule_" + name.replaceAll("[^\\w$]", "_"); // make sure the class name does not exist, if it does increase the counter int counter = -1; boolean exists = true; while (exists) { counter++; final String fileName = packageName.replaceAll("\\.", "/") + newName + "_" + counter + ext; exists = src.isAvailable(fileName); } // we have duplicate file names so append counter if (counter >= 0) { newName = newName + "_" + counter; } return newName; } private void loadCompiler() { switch (configuration.getCompiler()) { case PackageBuilderConfiguration.JANINO: { if (!"1.4".intern().equals(configuration.getJavaLanguageLevel())) throw new RuntimeDroolsException("Incompatible Java language level with selected compiler"); compiler = JavaCompilerFactory.getInstance().createCompiler("janino"); } case PackageBuilderConfiguration.CSHARP: { // Use reflection, since this classname will eventually be a configuration // parameter. try { Object o = Class.forName("org.drools.dotnet.semantics.DotnetCSharpCompiler, drools.dotnet") .newInstance(); compiler = (JavaCompiler) o; } catch (Exception ex) { throw new RuntimeException(ex); } break; } case PackageBuilderConfiguration.ECLIPSE: default: { EclipseJavaCompilerSettings eclipseSettings = new EclipseJavaCompilerSettings(); eclipseSettings.getMap().put("org.eclipse.jdt.core.compiler.codegen.targetPlatform", configuration.getJavaLanguageLevel()); eclipseSettings.getMap().put("org.eclipse.jdt.core.compiler.source", configuration.getJavaLanguageLevel()); compiler = new EclipseJavaCompiler(eclipseSettings); } } } private String ucFirst(final String name) { return name.toUpperCase().charAt(0) + name.substring(1); } public static class MissingPackageNameException extends IllegalArgumentException { private static final long serialVersionUID = 4056984379574366454L; public MissingPackageNameException(final String message) { super(message); } } /** * This is the super of the error handlers. * Each error handler knows how to report a compile error of its type, should it happen. * This is needed, as the compiling is done as one * hit at the end, and we need to be able to work out what rule/ast element * caused the error. * * An error handler it created for each class task that is queued to be compiled. * This doesn't mean an error has occurred, it just means it *may* occur * in the future and we need to be able to map it back to the AST element * that originally spawned the code to be compiled. */ public abstract static class ErrorHandler { private List errors = new ArrayList(); protected String message; public void addError(CompilationProblem err) { this.errors.add(err); } /** * * @return A DroolsError object populated as appropriate, * should the unthinkable happen and this need to be reported. */ public abstract DroolsError getError(); /** * We must use an error of JCI problem objects. * If there are no problems, null is returned. * These errors are placed in the DroolsError instances. * Its not 1 to 1 with reported errors. */ protected CompilationProblem[] collectCompilerProblems() { if (errors.size() == 0) { return null; } else { CompilationProblem[] list = new CompilationProblem[errors.size()]; errors.toArray(list); return list; } } } public static class RuleErrorHandler extends ErrorHandler { private PatternDescr descr; private Rule rule; public RuleErrorHandler(PatternDescr ruleDescr, Rule rule, String message) { this.descr = ruleDescr; this.rule = rule; this.message = message; } public DroolsError getError() { return new RuleError(rule, descr, collectCompilerProblems(), message); } } /** * There isn't much point in reporting invoker errors, as * they are no help. */ public static class RuleInvokerErrorHandler extends RuleErrorHandler { public RuleInvokerErrorHandler(PatternDescr ruleDescr, Rule rule, String message) { super(ruleDescr, rule, message); } } public static class FunctionErrorHandler extends ErrorHandler { private FunctionDescr descr; public FunctionErrorHandler(FunctionDescr functionDescr, String message) { this.descr = functionDescr; this.message = message; } public DroolsError getError() { return new FunctionError(descr, collectCompilerProblems(), message); } } private static JavaCompiler cachedJavaCompiler = null; }