001: package org.drools.rule.builder.dialect.java;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.HashMap;
006: import java.util.Iterator;
007: import java.util.List;
008: import java.util.Map;
009: import java.util.Set;
010:
011: import org.drools.base.ClassFieldExtractorCache;
012: import org.drools.base.TypeResolver;
013: import org.drools.commons.jci.compilers.CompilationResult;
014: import org.drools.commons.jci.compilers.JavaCompiler;
015: import org.drools.commons.jci.compilers.JavaCompilerFactory;
016: import org.drools.commons.jci.compilers.JavaCompilerSettings;
017: import org.drools.commons.jci.problems.CompilationProblem;
018: import org.drools.commons.jci.readers.MemoryResourceReader;
019: import org.drools.commons.jci.readers.ResourceReader;
020: import org.drools.compiler.Dialect;
021: import org.drools.compiler.PackageBuilder;
022: import org.drools.compiler.RuleError;
023: import org.drools.compiler.PackageBuilder.ErrorHandler;
024: import org.drools.compiler.PackageBuilder.FunctionErrorHandler;
025: import org.drools.compiler.PackageBuilder.RuleErrorHandler;
026: import org.drools.compiler.PackageBuilder.RuleInvokerErrorHandler;
027: import org.drools.lang.descr.AccumulateDescr;
028: import org.drools.lang.descr.AndDescr;
029: import org.drools.lang.descr.BaseDescr;
030: import org.drools.lang.descr.CollectDescr;
031: import org.drools.lang.descr.EvalDescr;
032: import org.drools.lang.descr.ExistsDescr;
033: import org.drools.lang.descr.ForallDescr;
034: import org.drools.lang.descr.FromDescr;
035: import org.drools.lang.descr.FunctionDescr;
036: import org.drools.lang.descr.NotDescr;
037: import org.drools.lang.descr.OrDescr;
038: import org.drools.lang.descr.PatternDescr;
039: import org.drools.lang.descr.QueryDescr;
040: import org.drools.lang.descr.RuleDescr;
041: import org.drools.rule.LineMappings;
042: import org.drools.rule.Package;
043: import org.drools.rule.Rule;
044: import org.drools.rule.builder.AccumulateBuilder;
045: import org.drools.rule.builder.CollectBuilder;
046: import org.drools.rule.builder.ConsequenceBuilder;
047: import org.drools.rule.builder.ForallBuilder;
048: import org.drools.rule.builder.FromBuilder;
049: import org.drools.rule.builder.FunctionBuilder;
050: import org.drools.rule.builder.GroupElementBuilder;
051: import org.drools.rule.builder.PatternBuilder;
052: import org.drools.rule.builder.PredicateBuilder;
053: import org.drools.rule.builder.QueryBuilder;
054: import org.drools.rule.builder.ReturnValueBuilder;
055: import org.drools.rule.builder.RuleBuildContext;
056: import org.drools.rule.builder.RuleClassBuilder;
057: import org.drools.rule.builder.RuleConditionBuilder;
058: import org.drools.rule.builder.SalienceBuilder;
059: import org.drools.rule.builder.dialect.mvel.MVELFromBuilder;
060: import org.drools.rule.builder.dialect.mvel.MVELSalienceBuilder;
061: import org.drools.util.StringUtils;
062:
063: public class JavaDialect implements Dialect {
064:
065: public static final String ID = "JavaDialect";
066:
067: private final static String EXPRESSION_DIALECT_NAME = "MVEL";
068: // builders
069: private final PatternBuilder pattern = new PatternBuilder();
070: private final QueryBuilder query = new QueryBuilder();
071: private final SalienceBuilder salience = new MVELSalienceBuilder();
072: private final JavaAccumulateBuilder accumulate = new JavaAccumulateBuilder();
073: private final JavaEvalBuilder eval = new JavaEvalBuilder();
074: private final JavaPredicateBuilder predicate = new JavaPredicateBuilder();
075: private final JavaReturnValueBuilder returnValue = new JavaReturnValueBuilder();
076: private final JavaConsequenceBuilder consequence = new JavaConsequenceBuilder();
077: private final JavaRuleClassBuilder rule = new JavaRuleClassBuilder();
078: private final MVELFromBuilder from = new MVELFromBuilder();
079: private final JavaFunctionBuilder function = new JavaFunctionBuilder();
080: private final CollectBuilder collect = new CollectBuilder();
081: private final ForallBuilder forall = new ForallBuilder();
082:
083: //
084: private KnowledgeHelperFixer knowledgeHelperFixer;
085: private DeclarationTypeFixer typeFixer;
086: private JavaExprAnalyzer analyzer;
087:
088: private JavaDialectConfiguration configuration;
089:
090: private Package pkg;
091: private JavaCompiler compiler;
092: private List generatedClassList;
093: private MemoryResourceReader src;
094: private PackageStore packageStoreWrapper;
095: private Map errorHandlers;
096: private List results;
097: // the class name for the rule
098: private String ruleClass;
099:
100: private TypeResolver typeResolver;
101: private ClassFieldExtractorCache classFieldExtractorCache;
102:
103: // a map of registered builders
104: private Map builders;
105:
106: public JavaDialect() {
107:
108: }
109:
110: public void init(PackageBuilder builder) {
111: this .pkg = builder.getPackage();
112: this .configuration = (JavaDialectConfiguration) builder
113: .getPackageBuilderConfiguration()
114: .getDialectConfiguration("java");
115: this .typeResolver = builder.getTypeResolver();
116: this .classFieldExtractorCache = builder
117: .getClassFieldExtractorCache();
118:
119: this .knowledgeHelperFixer = new KnowledgeHelperFixer();
120: this .typeFixer = new DeclarationTypeFixer();
121: this .analyzer = new JavaExprAnalyzer();
122:
123: if (pkg != null) {
124: init(pkg);
125: }
126:
127: initBuilder();
128:
129: loadCompiler();
130: }
131:
132: public void initBuilder() {
133: // statically adding all builders to the map
134: // but in the future we can move that to a configuration
135: // if we want to
136: this .builders = new HashMap();
137:
138: this .builders.put(CollectDescr.class, collect);
139:
140: this .builders.put(ForallDescr.class, forall);
141:
142: final GroupElementBuilder gebuilder = new GroupElementBuilder();
143:
144: this .builders.put(AndDescr.class, gebuilder);
145:
146: this .builders.put(OrDescr.class, gebuilder);
147:
148: this .builders.put(NotDescr.class, gebuilder);
149:
150: this .builders.put(ExistsDescr.class, gebuilder);
151:
152: this .builders.put(PatternDescr.class, getPatternBuilder());
153:
154: this .builders.put(QueryDescr.class, getQueryBuilder());
155:
156: this .builders.put(FromDescr.class, getFromBuilder());
157:
158: this .builders
159: .put(AccumulateDescr.class, getAccumulateBuilder());
160:
161: this .builders.put(EvalDescr.class, getEvalBuilder());
162: }
163:
164: public Map getBuilders() {
165: return this .builders;
166: }
167:
168: public void init(final Package pkg) {
169:
170: this .pkg = pkg;
171: //TODO Consider lazy init for these as they might have been initialized from the constructor and maybe used meanwhile
172: this .errorHandlers = new HashMap();
173: this .results = new ArrayList();
174:
175: this .src = new MemoryResourceReader();
176:
177: this .generatedClassList = new ArrayList();
178:
179: this .packageStoreWrapper = new PackageStore(pkg
180: .getPackageCompilationData(), this .results);
181: }
182:
183: public void init(final RuleDescr ruleDescr) {
184: final String ruleClassName = getUniqueLegalName(this .pkg
185: .getName(), ruleDescr.getName(), "java", this .src);
186: ruleDescr.setClassName(StringUtils.ucFirst(ruleClassName));
187: ruleDescr.setDialect(this );
188: }
189:
190: public void setRuleClass(final String ruleClass) {
191: this .ruleClass = ruleClass;
192: }
193:
194: public String getExpressionDialectName() {
195: return EXPRESSION_DIALECT_NAME;
196: }
197:
198: public AnalysisResult analyzeExpression(
199: final RuleBuildContext context, final BaseDescr descr,
200: final Object content) {
201: JavaAnalysisResult result = null;
202: try {
203: result = this .analyzer.analyzeExpression((String) content,
204: new Set[] {
205: context.getDeclarationResolver()
206: .getDeclarations().keySet(),
207: context.getPkg().getGlobals().keySet() });
208: } catch (final Exception e) {
209: context.getErrors().add(
210: new RuleError(context.getRule(), descr, e,
211: "Unable to determine the used declarations.\n"
212: + e));
213: }
214: return result;
215: }
216:
217: public AnalysisResult analyzeBlock(final RuleBuildContext context,
218: final BaseDescr descr, final String text) {
219: JavaAnalysisResult result = null;
220: try {
221: result = this .analyzer.analyzeBlock(text, new Set[] {
222: context.getDeclarationResolver().getDeclarations()
223: .keySet(),
224: context.getPkg().getGlobals().keySet() });
225: } catch (final Exception e) {
226: context.getErrors().add(
227: new RuleError(context.getRule(), descr, e,
228: "Unable to determine the used declarations.\n"
229: + e));
230: }
231: return result;
232: }
233:
234: /**
235: * Returns the current type resolver instance
236: * @return
237: */
238: public TypeResolver getTypeResolver() {
239: return this .typeResolver;
240: }
241:
242: /**
243: * Returns the cache of field extractors
244: * @return
245: */
246: public ClassFieldExtractorCache getClassFieldExtractorCache() {
247: return this .classFieldExtractorCache;
248: }
249:
250: /**
251: * Returns the Knowledge Helper Fixer
252: * @return
253: */
254: public KnowledgeHelperFixer getKnowledgeHelperFixer() {
255: return this .knowledgeHelperFixer;
256: }
257:
258: /**
259: * @return the typeFixer
260: */
261: public DeclarationTypeFixer getTypeFixer() {
262: return this .typeFixer;
263: }
264:
265: public RuleConditionBuilder getBuilder(final Class clazz) {
266: return (RuleConditionBuilder) this .builders.get(clazz);
267: }
268:
269: public PatternBuilder getPatternBuilder() {
270: return this .pattern;
271: }
272:
273: public QueryBuilder getQueryBuilder() {
274: return this .query;
275: }
276:
277: public SalienceBuilder getSalienceBuilder() {
278: return this .salience;
279: }
280:
281: public AccumulateBuilder getAccumulateBuilder() {
282: return this .accumulate;
283: }
284:
285: public RuleConditionBuilder getEvalBuilder() {
286: return this .eval;
287: }
288:
289: public PredicateBuilder getPredicateBuilder() {
290: return this .predicate;
291: }
292:
293: public ReturnValueBuilder getReturnValueBuilder() {
294: return this .returnValue;
295: }
296:
297: public ConsequenceBuilder getConsequenceBuilder() {
298: return this .consequence;
299: }
300:
301: public RuleClassBuilder getRuleClassBuilder() {
302: return this .rule;
303: }
304:
305: public FunctionBuilder getFunctionBuilder() {
306: return this .function;
307: }
308:
309: public FromBuilder getFromBuilder() {
310: return this .from;
311: }
312:
313: /**
314: * This actually triggers the compiling of all the resources.
315: * Errors are mapped back to the element that originally generated the semantic
316: * code.
317: */
318: public void compileAll() {
319: if (this .generatedClassList.isEmpty()) {
320: return;
321: }
322: final String[] classes = new String[this .generatedClassList
323: .size()];
324: this .generatedClassList.toArray(classes);
325:
326: final CompilationResult result = this .compiler.compile(classes,
327: this .src, this .packageStoreWrapper, this .pkg
328: .getPackageCompilationData().getClassLoader());
329:
330: //this will sort out the errors based on what class/file they happened in
331: if (result.getErrors().length > 0) {
332: for (int i = 0; i < result.getErrors().length; i++) {
333: final CompilationProblem err = result.getErrors()[i];
334:
335: // System.out.println("Line: "+err.getStartLine());
336: // LineMappings maps = this.pkg.getPackageCompilationData().getLineMappings( err.getFileName().replace( '/', '.' ).substring( 0, err.getFileName().length() - 5 ) );
337: // int line = err.getStartLine() + maps.getStartLine() - maps.getOffset() -1;
338: // System.out.println("Map: "+line);
339: final ErrorHandler handler = (ErrorHandler) this .errorHandlers
340: .get(err.getFileName());
341: if (handler instanceof RuleErrorHandler) {
342: final RuleErrorHandler rh = (RuleErrorHandler) handler;
343: }
344: handler.addError(err);
345: }
346:
347: final Collection errors = this .errorHandlers.values();
348: for (final Iterator iter = errors.iterator(); iter
349: .hasNext();) {
350: final ErrorHandler handler = (ErrorHandler) iter.next();
351: if (handler.isInError()) {
352: if (!(handler instanceof RuleInvokerErrorHandler)) {
353: this .results.add(handler.getError());
354: } else {
355: //we don't really want to report invoker errors.
356: //mostly as they can happen when there is a syntax error in the RHS
357: //and otherwise, it is a programmatic error in drools itself.
358: //throw new RuntimeException( "Warning: An error occurred compiling a semantic invoker. Errors should have been reported elsewhere." + handler.getError() );
359: }
360: }
361: }
362: }
363:
364: // We've compiled everthing, so clear it for the next set of additions
365: this .generatedClassList.clear();
366: }
367:
368: /**
369: * This will add the rule for compiling later on.
370: * It will not actually call the compiler
371: */
372: public void addRule(final RuleBuildContext context) {
373: // return if there is no ruleclass name;
374: if (this .ruleClass == null) {
375: return;
376: }
377:
378: final Rule rule = context.getRule();
379: final RuleDescr ruleDescr = context.getRuleDescr();
380:
381: // The compilation result is for th entire rule, so difficult to associate with any descr
382: addClassCompileTask(this .pkg.getName() + "."
383: + ruleDescr.getClassName(), ruleDescr, this .ruleClass,
384: this .src, new RuleErrorHandler(ruleDescr, rule,
385: "Rule Compilation error"));
386:
387: for (final Iterator it = context.getInvokers().keySet()
388: .iterator(); it.hasNext();) {
389: final String className = (String) it.next();
390:
391: // Check if an invoker - returnvalue, predicate, eval or consequence has been associated
392: // If so we add it to the PackageCompilationData as it will get wired up on compilation
393: final Object invoker = context.getInvokerLookups().get(
394: className);
395: if (invoker != null) {
396: this .pkg.getPackageCompilationData().putInvoker(
397: className, invoker);
398: }
399: final String text = (String) context.getInvokers().get(
400: className);
401:
402: final BaseDescr descr = (BaseDescr) context
403: .getDescrLookups().get(className);
404: addClassCompileTask(className, descr, text, this .src,
405: new RuleInvokerErrorHandler(descr, rule,
406: "Unable to generate rule invoker."));
407:
408: }
409:
410: // setup the line mappins for this rule
411: final String name = this .pkg.getName() + "."
412: + StringUtils.ucFirst(ruleDescr.getClassName());
413: final LineMappings mapping = new LineMappings(name);
414: mapping.setStartLine(ruleDescr.getConsequenceLine());
415: mapping.setOffset(ruleDescr.getConsequenceOffset());
416:
417: context.getPkg().getPackageCompilationData().getLineMappings()
418: .put(name, mapping);
419:
420: }
421:
422: public void addFunction(final FunctionDescr functionDescr,
423: final TypeResolver typeResolver) {
424:
425: final String functionClassName = this .pkg.getName() + "."
426: + StringUtils.ucFirst(functionDescr.getName());
427: this .pkg.addFunction(functionDescr.getName());
428:
429: final String functionSrc = getFunctionBuilder().build(this .pkg,
430: functionDescr, typeResolver,
431: pkg.getPackageCompilationData().getLineMappings(),
432: this .results);
433:
434: addClassCompileTask(functionClassName, functionDescr,
435: functionSrc, this .src, new FunctionErrorHandler(
436: functionDescr, "Function Compilation error"));
437:
438: final LineMappings mapping = new LineMappings(functionClassName);
439: mapping.setStartLine(functionDescr.getLine());
440: mapping.setOffset(functionDescr.getOffset());
441: pkg.getPackageCompilationData().getLineMappings().put(
442: functionClassName, mapping);
443: }
444:
445: /**
446: * This adds a compile "task" for when the compiler of
447: * semantics (JCI) is called later on with compileAll()\
448: * which actually does the compiling.
449: * The ErrorHandler is required to map the errors back to the
450: * element that caused it.
451: */
452: private void addClassCompileTask(final String className,
453: final BaseDescr descr, final String text,
454: final MemoryResourceReader src, final ErrorHandler handler) {
455:
456: final String fileName = className.replace('.', '/') + ".java";
457:
458: src.add(fileName, text.getBytes());
459:
460: this .errorHandlers.put(fileName, handler);
461:
462: addClassName(fileName);
463: }
464:
465: public void addClassName(final String className) {
466: this .generatedClassList.add(className);
467: }
468:
469: private void loadCompiler() {
470: switch (this .configuration.getCompiler()) {
471: case JavaDialectConfiguration.JANINO: {
472: this .compiler = JavaCompilerFactory.getInstance()
473: .createCompiler("janino");
474: break;
475: }
476: case JavaDialectConfiguration.ECLIPSE:
477: default: {
478: this .compiler = JavaCompilerFactory.getInstance()
479: .createCompiler("eclipse");
480: JavaCompilerSettings settings = this .compiler
481: .createDefaultSettings();
482:
483: String lngLevel = this .configuration.getJavaLanguageLevel();
484: settings.setTargetVersion(lngLevel);
485:
486: if (lngLevel == "1.4") {
487: // 1.5 is the minimum for source langauge level, so we can use static imports.
488: lngLevel = "1.5";
489: }
490:
491: settings.setSourceVersion(lngLevel);
492: break;
493: }
494: }
495: }
496:
497: public void addImport(String importEntry) {
498: // we don't need to do anything here
499: }
500:
501: public void addStaticImport(String staticImportEntry) {
502: // we don't need to do anything here
503: }
504:
505: public List getResults() {
506: return this .results;
507: }
508:
509: /**
510: * 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.
511: *
512: * TODO: move out to shared utility class
513: *
514: * @param packageName
515: * @param name
516: * @param ext
517: * @return
518: */
519: public static String getUniqueLegalName(final String packageName,
520: final String name, final String ext,
521: final ResourceReader src) {
522: // replaces all non alphanumeric or $ chars with _
523: String newName = "Rule_" + name.replaceAll("[[^\\w]$]", "_");
524:
525: // make sure the class name does not exist, if it does increase the counter
526: int counter = -1;
527: boolean exists = true;
528: while (exists) {
529:
530: counter++;
531: final String fileName = packageName.replaceAll("\\.", "/")
532: + newName + "_" + counter + ext;
533:
534: //MVEL:test null to Fix failing test on org.drools.rule.builder.dialect.mvel.MVELConsequenceBuilderTest.testImperativeCodeError()
535: exists = src != null && src.isAvailable(fileName);
536: }
537: // we have duplicate file names so append counter
538: if (counter >= 0) {
539: newName = newName + "_" + counter;
540: }
541:
542: return newName;
543: }
544:
545: public String getId() {
546: return ID;
547: }
548: }
|