package org.drools.rule.builder.dialect.java; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.drools.base.TypeResolver; import org.drools.commons.jci.compilers.CompilationResult; import org.drools.commons.jci.compilers.JavaCompiler; import org.drools.commons.jci.compilers.JavaCompilerFactory; import org.drools.commons.jci.compilers.JavaCompilerSettings; import org.drools.commons.jci.problems.CompilationProblem; import org.drools.commons.jci.readers.MemoryResourceReader; import org.drools.commons.jci.readers.ResourceReader; import org.drools.compiler.DescrBuildError; import org.drools.compiler.Dialect; import org.drools.compiler.PackageBuilder; import org.drools.compiler.PackageRegistry; import org.drools.compiler.PackageBuilder.ErrorHandler; import org.drools.compiler.PackageBuilder.FunctionErrorHandler; import org.drools.compiler.PackageBuilder.RuleErrorHandler; import org.drools.compiler.PackageBuilder.RuleInvokerErrorHandler; import org.drools.compiler.PackageBuilder.SrcErrorHandler; import org.drools.core.util.StringUtils; import org.drools.io.Resource; import org.drools.io.internal.InternalResource; import org.drools.lang.descr.AccumulateDescr; import org.drools.lang.descr.AndDescr; import org.drools.lang.descr.BaseDescr; import org.drools.lang.descr.CollectDescr; import org.drools.lang.descr.EntryPointDescr; import org.drools.lang.descr.EvalDescr; import org.drools.lang.descr.ExistsDescr; import org.drools.lang.descr.ForallDescr; import org.drools.lang.descr.FromDescr; import org.drools.lang.descr.FunctionDescr; import org.drools.lang.descr.NotDescr; import org.drools.lang.descr.OrDescr; import org.drools.lang.descr.PatternDescr; import org.drools.lang.descr.ProcessDescr; import org.drools.lang.descr.QueryDescr; import org.drools.lang.descr.RuleDescr; import org.drools.rule.Function; import org.drools.rule.JavaDialectRuntimeData; import org.drools.rule.LineMappings; import org.drools.rule.Package; import org.drools.rule.Rule; import org.drools.rule.builder.AccumulateBuilder; import org.drools.rule.builder.CollectBuilder; import org.drools.rule.builder.ConsequenceBuilder; import org.drools.rule.builder.EnabledBuilder; import org.drools.rule.builder.EntryPointBuilder; import org.drools.rule.builder.ForallBuilder; import org.drools.rule.builder.FromBuilder; import org.drools.rule.builder.FunctionBuilder; import org.drools.rule.builder.GroupElementBuilder; import org.drools.rule.builder.PackageBuildContext; import org.drools.rule.builder.PatternBuilder; import org.drools.rule.builder.PredicateBuilder; import org.drools.rule.builder.QueryBuilder; import org.drools.rule.builder.ReturnValueBuilder; import org.drools.rule.builder.RuleBuildContext; import org.drools.rule.builder.RuleClassBuilder; import org.drools.rule.builder.RuleConditionBuilder; import org.drools.rule.builder.SalienceBuilder; import org.drools.rule.builder.dialect.mvel.MVELEnabledBuilder; import org.drools.rule.builder.dialect.mvel.MVELFromBuilder; import org.drools.rule.builder.dialect.mvel.MVELSalienceBuilder; public class JavaDialect implements Dialect { public static final String ID = "java"; private final static String EXPRESSION_DIALECT_NAME = "mvel"; // builders private static final PatternBuilder PATTERN_BUILDER = new PatternBuilder(); private static final QueryBuilder QUERY_BUILDER = new QueryBuilder(); private static final SalienceBuilder SALIENCE_BUILDER = new MVELSalienceBuilder(); private static final EnabledBuilder ENABLED_BUILDER = new MVELEnabledBuilder(); private static final JavaAccumulateBuilder ACCUMULATE_BUILDER = new JavaAccumulateBuilder(); private static final JavaEvalBuilder EVAL_BUILDER = new JavaEvalBuilder(); private static final JavaPredicateBuilder PREDICATE_BUILDER = new JavaPredicateBuilder(); private static final JavaReturnValueBuilder RETURN_VALUE_BUILDER = new JavaReturnValueBuilder(); private static final JavaConsequenceBuilder CONSESUENCE_BUILDER = new JavaConsequenceBuilder(); private static final JavaRuleClassBuilder RULE_CLASS_BUILDER = new JavaRuleClassBuilder(); private static final MVELFromBuilder FROM_BUILDER = new MVELFromBuilder(); private static final JavaFunctionBuilder FUNCTION_BUILDER = new JavaFunctionBuilder(); private static final CollectBuilder COLLECT_BUIDER = new CollectBuilder(); private static final ForallBuilder FORALL_BUILDER = new ForallBuilder(); private static final EntryPointBuilder ENTRY_POINT_BUILDER = new EntryPointBuilder(); private static final GroupElementBuilder GE_BUILDER = new GroupElementBuilder(); // a map of registered builders private static Map builders; static { initBuilder(); } // private static final KnowledgeHelperFixer knowledgeHelperFixer = new KnowledgeHelperFixer(); private static final DeclarationTypeFixer typeFixer = new DeclarationTypeFixer(); private static final JavaExprAnalyzer analyzer = new JavaExprAnalyzer(); private JavaDialectConfiguration configuration; private Package pkg; private JavaCompiler compiler; private List generatedClassList; private MemoryResourceReader src; private PackageStore packageStoreWrapper; private Map errorHandlers; private List results; private PackageBuilder packageBuilder; private PackageRegistry packageRegistry; public JavaDialect(PackageBuilder builder, PackageRegistry pkgRegistry, Package pkg) { this.packageBuilder = builder; this.pkg = pkg; this.packageRegistry = pkgRegistry; this.configuration = (JavaDialectConfiguration) builder.getPackageBuilderConfiguration().getDialectConfiguration( "java" ); this.errorHandlers = new HashMap(); this.results = new ArrayList(); this.src = new MemoryResourceReader(); this.generatedClassList = new ArrayList(); JavaDialectRuntimeData data = null; // initialie the dialect runtime data if it doesn't already exist if ( pkg.getDialectRuntimeRegistry().getDialectData( ID ) == null ) { data = new JavaDialectRuntimeData(); this.pkg.getDialectRuntimeRegistry().setDialectData( ID, data ); data.onAdd( this.pkg.getDialectRuntimeRegistry(), this.packageBuilder.getRootClassLoader() ); } this.packageStoreWrapper = new PackageStore( data, this.results ); loadCompiler(); } public static synchronized void initBuilder() { if ( builders != null ) { return; } // statically adding all builders to the map // but in the future we can move that to a configuration // if we want to builders = new HashMap(); builders.put( CollectDescr.class, COLLECT_BUIDER ); builders.put( ForallDescr.class, FORALL_BUILDER ); builders.put( AndDescr.class, GE_BUILDER ); builders.put( OrDescr.class, GE_BUILDER ); builders.put( NotDescr.class, GE_BUILDER ); builders.put( ExistsDescr.class, GE_BUILDER ); builders.put( PatternDescr.class, PATTERN_BUILDER ); builders.put( QueryDescr.class, QUERY_BUILDER ); builders.put( FromDescr.class, FROM_BUILDER ); builders.put( AccumulateDescr.class, ACCUMULATE_BUILDER ); builders.put( EvalDescr.class, EVAL_BUILDER ); builders.put( EntryPointDescr.class, ENTRY_POINT_BUILDER ); } public Map getBuilders() { return this.builders; } public void init(final RuleDescr ruleDescr) { final String ruleClassName = getUniqueLegalName( this.pkg.getName(), ruleDescr.getName(), "java", "Rule", this.src ); ruleDescr.setClassName( StringUtils.ucFirst( ruleClassName ) ); } public void init(final ProcessDescr processDescr) { final String processDescrClassName = getUniqueLegalName( this.pkg.getName(), processDescr.getName(), "java", "Process", this.src ); processDescr.setClassName( StringUtils.ucFirst( processDescrClassName ) ); } public String getExpressionDialectName() { return EXPRESSION_DIALECT_NAME; } public AnalysisResult analyzeExpression(final PackageBuildContext context, final BaseDescr descr, final Object content, final Map<String, Class< ? >>[] availableIdentifiers) { JavaAnalysisResult result = null; try { result = this.analyzer.analyzeExpression( (String) content, availableIdentifiers ); } catch ( final Exception e ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), descr, e, "Unable to determine the used declarations.\n" + e ) ); } return result; } public AnalysisResult analyzeBlock(final PackageBuildContext context, final BaseDescr descr, final String text, final Map<String, Class< ? >>[] availableIdentifiers) { JavaAnalysisResult result = null; try { result = this.analyzer.analyzeBlock( text, availableIdentifiers ); } catch ( final Exception e ) { context.getErrors().add( new DescrBuildError( context.getParentDescr(), descr, e, "Unable to determine the used declarations.\n" + e ) ); } return result; } /** * Returns the current type resolver instance * * @return */ public TypeResolver getTypeResolver() { return this.packageRegistry.getTypeResolver(); } /** * Returns the Knowledge Helper Fixer * * @return */ public KnowledgeHelperFixer getKnowledgeHelperFixer() { return this.knowledgeHelperFixer; } /** * @return the typeFixer */ public DeclarationTypeFixer getTypeFixer() { return this.typeFixer; } public RuleConditionBuilder getBuilder(final Class clazz) { return (RuleConditionBuilder) this.builders.get( clazz ); } public PatternBuilder getPatternBuilder() { return PATTERN_BUILDER; } public QueryBuilder getQueryBuilder() { return QUERY_BUILDER; } public SalienceBuilder getSalienceBuilder() { return SALIENCE_BUILDER; } public EnabledBuilder getEnabledBuilder() { return ENABLED_BUILDER; } public AccumulateBuilder getAccumulateBuilder() { return ACCUMULATE_BUILDER; } public RuleConditionBuilder getEvalBuilder() { return EVAL_BUILDER; } public PredicateBuilder getPredicateBuilder() { return PREDICATE_BUILDER; } public ReturnValueBuilder getReturnValueBuilder() { return RETURN_VALUE_BUILDER; } public ConsequenceBuilder getConsequenceBuilder() { return CONSESUENCE_BUILDER; } public RuleClassBuilder getRuleClassBuilder() { return RULE_CLASS_BUILDER; } public FunctionBuilder getFunctionBuilder() { return FUNCTION_BUILDER; } public FromBuilder getFromBuilder() { return FROM_BUILDER; } public EntryPointBuilder getEntryPointBuilder() { return ENTRY_POINT_BUILDER; } /** * This actually triggers the compiling of all the resources. * Errors are mapped back to the element that originally generated the semantic * code. */ public void compileAll() { if ( this.generatedClassList.isEmpty() ) { return; } final String[] classes = new String[this.generatedClassList.size()]; this.generatedClassList.toArray( classes ); File dumpDir = this.configuration.getPackageBuilderConfiguration().getDumpDir(); if ( dumpDir != null ) { dumpResources( classes, dumpDir ); } final CompilationResult result = this.compiler.compile( classes, this.src, this.packageStoreWrapper, this.packageBuilder.getRootClassLoader() ); //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++ ) { final CompilationProblem err = result.getErrors()[i]; final ErrorHandler handler = (ErrorHandler) this.errorHandlers.get( err.getFileName() ); if ( handler instanceof RuleErrorHandler ) { final RuleErrorHandler rh = (RuleErrorHandler) handler; } handler.addError( err ); } final Collection errors = this.errorHandlers.values(); for ( final Iterator iter = errors.iterator(); iter.hasNext(); ) { final ErrorHandler handler = (ErrorHandler) iter.next(); if ( handler.isInError() ) { this.results.add( handler.getError() ); } } } // We've compiled everthing, so clear it for the next set of additions this.generatedClassList.clear(); } /** * @param classes * @param dumpDir * @throws IOException * @throws FileNotFoundException */ private void dumpResources(final String[] classes, File dumpDir) { for ( int i = 0; i < classes.length; i++ ) { File target = new File( dumpDir, classes[i] ); FileOutputStream out = null; try { File parent = target.getParentFile(); if ( parent != null && !parent.exists() ) { parent.mkdirs(); } target.createNewFile(); out = new FileOutputStream( target ); out.write( this.src.getBytes( classes[i] ) ); } catch ( FileNotFoundException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } finally { if ( out != null ) try { out.close(); } catch ( Exception e ) { } } } } /** * This will add the rule for compiling later on. * It will not actually call the compiler */ public void addRule(final RuleBuildContext context) { RuleClassBuilder classBuilder = context.getDialect().getRuleClassBuilder(); String ruleClass = classBuilder.buildRule( context ); // return if there is no ruleclass name; if ( ruleClass == null ) { return; } final Rule rule = context.getRule(); final RuleDescr ruleDescr = context.getRuleDescr(); // The compilation result is for the entire rule, so difficult to associate with any descr addClassCompileTask( this.pkg.getName() + "." + ruleDescr.getClassName(), ruleDescr, ruleClass, this.src, new RuleErrorHandler( ruleDescr, rule, "Rule Compilation error" ) ); JavaDialectRuntimeData data = (JavaDialectRuntimeData) this.pkg.getDialectRuntimeRegistry().getDialectData( this.ID ); for ( final Iterator it = context.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 = context.getInvokerLookups().get( className ); if ( invoker != null ) { data.putInvoker( className, invoker ); } final String text = (String) context.getInvokers().get( className ); final BaseDescr descr = (BaseDescr) context.getDescrLookups().get( className ); addClassCompileTask( className, descr, text, this.src, new RuleInvokerErrorHandler( descr, rule, "Unable to generate rule invoker." ) ); } // setup the line mappins for this rule final String name = this.pkg.getName() + "." + StringUtils.ucFirst( ruleDescr.getClassName() ); final LineMappings mapping = new LineMappings( name ); mapping.setStartLine( ruleDescr.getConsequenceLine() ); mapping.setOffset( ruleDescr.getConsequenceOffset() ); this.pkg.getDialectRuntimeRegistry().getLineMappings().put( name, mapping ); } public void addFunction(final FunctionDescr functionDescr, final TypeResolver typeResolver, final Resource resource) { JavaDialectRuntimeData data = (JavaDialectRuntimeData) this.pkg.getDialectRuntimeRegistry().getDialectData( this.ID ); //System.out.println( functionDescr + " : " + typeResolver ); final String functionClassName = this.pkg.getName() + "." + StringUtils.ucFirst( functionDescr.getName() ); functionDescr.setClassName( functionClassName ); this.pkg.addStaticImport( functionClassName + "." + functionDescr.getName() ); Function function = new Function( functionDescr.getNamespace(), functionDescr.getName(), this.ID ); if ( resource != null && ((InternalResource) resource).hasURL() ) { function.setResource( resource ); } this.pkg.addFunction( function ); final String functionSrc = getFunctionBuilder().build( this.pkg, functionDescr, typeResolver, this.pkg.getDialectRuntimeRegistry().getLineMappings(), this.results ); addClassCompileTask( functionClassName, functionDescr, functionSrc, this.src, new FunctionErrorHandler( functionDescr, "Function Compilation error" ) ); final LineMappings mapping = new LineMappings( functionClassName ); mapping.setStartLine( functionDescr.getLine() ); mapping.setOffset( functionDescr.getOffset() ); this.pkg.getDialectRuntimeRegistry().getLineMappings().put( functionClassName, mapping ); } public void preCompileAddFunction(FunctionDescr functionDescr, TypeResolver typeResolver) { final String functionClassName = this.pkg.getName() + "." + StringUtils.ucFirst( functionDescr.getName() ); this.pkg.addStaticImport( functionClassName + "." + functionDescr.getName() ); } public void postCompileAddFunction(FunctionDescr functionDescr, TypeResolver typeResolver) { final String functionClassName = this.pkg.getName() + "." + StringUtils.ucFirst( functionDescr.getName() ); this.packageRegistry.addStaticImport( functionClassName + "." + functionDescr.getName() ); } public void addSrc(String resourceName, byte[] content) { src.add( resourceName, content ); this.errorHandlers.put( resourceName, new SrcErrorHandler( "Src compile error" ) ); addClassName( resourceName ); } /** * 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. */ public void addClassCompileTask(final String className, final BaseDescr descr, final String text, final MemoryResourceReader src, final ErrorHandler handler) { final String fileName = className.replace( '.', '/' ) + ".java"; if (src != null) { src.add( fileName, text.getBytes() ); } else { this.src.add( fileName, text.getBytes() ); } this.errorHandlers.put( fileName, handler ); addClassName( fileName ); } public void addClassName(final String className) { this.generatedClassList.add( className ); } private void loadCompiler() { switch ( this.configuration.getCompiler() ) { case JavaDialectConfiguration.JANINO : { this.compiler = JavaCompilerFactory.getInstance().createCompiler( "janino" ); break; } case JavaDialectConfiguration.ECLIPSE : default : { this.compiler = JavaCompilerFactory.getInstance().createCompiler( "eclipse" ); JavaCompilerSettings settings = this.compiler.createDefaultSettings(); String lngLevel = this.configuration.getJavaLanguageLevel(); settings.setTargetVersion( lngLevel ); settings.setSourceVersion( lngLevel ); break; } } } public void addImport(String importEntry) { // we don't need to do anything here } public void addStaticImport(String staticImportEntry) { // we don't need to do anything here } public List getResults() { return this.results; } /** * 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. * <p/> * TODO: move out to shared utility class * * @param packageName * @param name * @param ext * @return */ public static String getUniqueLegalName(final String packageName, final String name, final String ext, final String prefix, final ResourceReader src) { // replaces all non alphanumeric or $ chars with _ String newName = prefix + "_" + name.replaceAll( "[ -/:-@\\[-`\\{-\\xff]", "_" ); // 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; //MVEL:test null to Fix failing test on org.drools.rule.builder.dialect.mvel.MVELConsequenceBuilderTest.testImperativeCodeError() exists = src != null && src.isAvailable( fileName ); } // we have duplicate file names so append counter if ( counter >= 0 ) { newName = newName + "_" + counter; } return newName; } public String getId() { return ID; } }