/** * Copyright 2010 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. */ package org.jboss.drools.guvnor.importgenerator; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.drools.common.DroolsObjectOutputStream; import org.drools.compiler.DroolsError; import org.drools.compiler.DroolsParserException; import org.drools.compiler.PackageBuilder; import org.drools.decisiontable.InputType; import org.drools.rule.Package; import org.jboss.drools.guvnor.importgenerator.CmdArgsParser.Parameters; import org.jboss.drools.guvnor.importgenerator.utils.DroolsHelper; import org.jboss.drools.guvnor.importgenerator.utils.FileIO; /** * Represents a drl package file found in the file system * * @author <a href="mailto:mallen@redhat.com">Mat Allen</a> */ public class PackageFile { private static final String PH_RULE_START="rule "; private static final String PH_PACKAGE_START="package "; private static final String PH_NEWLINE="\n"; private static final String[] RULE_START_MATCHERS=new String[]{"rule \"", "rule\""}; private static final String[] RULE_END_MATCHERS=new String[] { "end\n", "\nend" }; private static final String PACKAGE_DELIMETER = "."; private static String FUNCTIONS_FILE=null; private Package pkg; private File file; private String imports=""; //default to no imports private String dependencyErrors=""; private String compilationErrors=""; private Map<String, Rule> rules=new HashMap<String, Rule>(); private List<File> ruleFiles=new ArrayList<File>(); private String name; private enum Format{ DRL(".drl"), XLS(".xls"); String value; Format(String value){ this.value=value; } } /** * goes through the file system calling extract to build a list of PackageFile objects * @param options * @return * @throws Exception */ public static Map<String, PackageFile> buildPackages(CmdArgsParser options) throws Exception{ String path=options.getOption(Parameters.OPTIONS_PATH); FUNCTIONS_FILE=options.getOption(Parameters.OPTIONS_FUNCTIONS_FILE); Map<String, PackageFile> result = new HashMap<String, PackageFile>(); File location=new File(path); if (!location.isDirectory()) throw new Exception("path must be a directory"); buildPackageForDirectory(result, location, options); return result; } /** * Populates the <param>packages</param> parameter with PackageFile objects representing files within the specified <param>directory</param> * @param packages * @param directory * @param options * @throws FileNotFoundException * @throws UnsupportedEncodingException */ private static void buildPackageForDirectory(Map<String, PackageFile> packages, File directory, CmdArgsParser options) throws FileNotFoundException, UnsupportedEncodingException, DroolsParserException, IOException{ boolean recurse="true".equals(options.getOption(Parameters.OPTIONS_RECURSIVE)); File[] files=directory.listFiles( new FilenameFilter(){ public boolean accept(File dir, String name){ return !name.startsWith("."); }} ); for (int i = 0; i < files.length; i++) { //if it's a directory with files then build a package if (files[i].isDirectory()){ File[] ruleFiles=getRuleFiles(files[i], options); if (ruleFiles.length>0){ PackageFile packageFile=parseRuleFiles(ruleFiles, options); packageFile.setName(getPackageName(files[i], options)); packages.put(packageFile.getName(), packageFile); }else{ if (recurse) buildPackageForDirectory(packages, files[i], options); } } } } private static File[] getRuleFiles(File directory, CmdArgsParser options){ if (directory.isDirectory()){ final String extensionList = options.getOption(Parameters.OPTIONS_EXTENSIONS); File[] files=directory.listFiles( new FilenameFilter(){ public boolean accept(File dir, String name){ return !name.startsWith(".") && name.matches(buildRE(extensionList)); }} ); List<File> result=new ArrayList<File>(); for (int i = 0; i < files.length; i++) { File f = files[i]; if (f.isFile()) result.add(f); } return result.toArray(new File[result.size()]); } return new File[]{}; } private static PackageFile parseRuleFiles(File[] ruleFiles, CmdArgsParser options) throws IOException, DroolsParserException { PackageFile result=new PackageFile(); for (int i = 0; i < ruleFiles.length; i++) { File file = ruleFiles[i]; if (file.getName().endsWith(".drl")) { parseDrlFile(file, result, options); result.addRuleFile(file); } else if (file.getName().endsWith(".xls")) { if (result.getRuleFiles().size()>1){ //this is because the binary data needs a filename associated in the xml, and if there's multiple files when which one do you use? throw new DroolsParserException("Can't parse more than one .xls decision table file in a single directory ["+ file.getParentFile().getPath() +"]"); } parseXlsFile(file, result, options); result.addRuleFile(file); } } return result; } private static void parseXlsFile(File file, PackageFile packageFile, CmdArgsParser options) throws FileNotFoundException, UnsupportedEncodingException{ String content=FileIO.readAllAsBase64(file); packageFile.setName(getPackageName(file, options)); packageFile.setFile(file); packageFile.getRules().put(file.getName(), new Rule(file.getName(), content)); } private static void parseDrlFile(File file, PackageFile packageFile, CmdArgsParser options) throws FileNotFoundException{ String content = FileIO.readAll(new FileInputStream(file)); int packageLoc = content.indexOf(PH_PACKAGE_START); // usually 0 int ruleLoc = getRuleStart(content, 0);// variable //packageFile.setPackageName(getPackageName(file, options)); if (ruleLoc < 0) return; // there are no rule's in this file (perhaps functions or other?) String imports = content.substring(packageLoc, ruleLoc); packageFile.addImports(imports); try { boolean moreRules = true; while (moreRules) { int endLoc = getLoc(content, ruleLoc, RULE_END_MATCHERS) + 4; String ruleContents = content.substring(ruleLoc, endLoc); ruleLoc = getRuleStart(content, endLoc); moreRules = ruleLoc >= 0; Rule rule = new Rule(findRuleName(ruleContents, options), ruleContents); packageFile.getRules().put(rule.getRuleName(), rule); } } catch (StringIndexOutOfBoundsException e) { System.err.print("Error with file: " + file.getName() + "\n"); } } /** * compiles the rule files into a package and generates any error details * @throws IOException * @throws DroolsParserException */ public void buildPackage() throws IOException, DroolsParserException{ PackageBuilder pb = new PackageBuilder(); for (File file : getRuleFiles()) { if (FUNCTIONS_FILE!=null){ File functionsFile=new File(file.getParentFile().getPath(), FUNCTIONS_FILE); if (functionsFile.exists()){ pb.addPackageFromDrl(new FileReader(functionsFile)); } } if (isFormat(Format.DRL)){ pb.addPackageFromDrl(new FileReader(file)); }else if (isFormat(Format.DRL)){ pb.addPackageFromDrl(new StringReader(DroolsHelper.compileDTabletoDRL(file, InputType.XLS))); } } this.pkg=pb.getPackage(); if (pkg==null) { // compilation error - the rule is syntactically incorrect for (int i = 0; i < pb.getErrors().getErrors().length; i++) { DroolsError msg = pb.getErrors().getErrors()[i]; addCompilationError(msg.getMessage()); } } else if (pkg!=null && !pkg.isValid()) { addDependencyError(pkg.getErrorSummary()); } } /** impl that determines whether you have dependency errors * - this is not completed - unsure how to display/count error packages if you get one comp error and one dep error in a simple package */ public void buildPackageWithAccurateDependencyErrorDetection() throws IOException, DroolsParserException { PackageBuilder resBuilder = new PackageBuilder(); for (File file : getRuleFiles()) { PackageBuilder pb = new PackageBuilder(); if (FUNCTIONS_FILE != null) { File functionsFile = new File(file.getParentFile().getPath(), FUNCTIONS_FILE); if (functionsFile.exists()) { pb.addPackageFromDrl(new FileReader(functionsFile)); } } if (isFormat(Format.DRL)){ pb.addPackageFromDrl(new FileReader(file)); }else if (isFormat(Format.DRL)){ pb.addPackageFromDrl(new StringReader(DroolsHelper.compileDTabletoDRL(file, InputType.XLS))); } Package check=pb.getPackage(); if (check == null) { // compilation error - the rule is syntactically incorrect for (int i = 0; i < pb.getErrors().getErrors().length; i++) { DroolsError msg = pb.getErrors().getErrors()[i]; addCompilationError(msg.getMessage()); } } else if (check != null && !check.isValid()) { addDependencyError(check.getErrorSummary()); resBuilder.addPackage(pb.getPackage()); } resBuilder.addPackage(check); } this.pkg=resBuilder.getPackage(); } /** * * @return * @throws IOException */ public byte[] toByteArray() throws IOException{ if (pkg!=null){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); DroolsObjectOutputStream doos = new DroolsObjectOutputStream(baos); doos.writeObject(pkg); return baos.toByteArray(); }else{ return new byte[]{}; // throw new IOException("Package not built yet"); } } public void addRuleFile(File ruleFile){ ruleFiles.add(ruleFile); } public List<File> getRuleFiles() { return ruleFiles; } // public String getCompiledPackage() throws UnsupportedEncodingException, DroolsParserException, IOException{ // return FileIO.toBase64(DroolsHelper.compileRuletoPKG(this)); // } /** * Given a comma separated list of file extensions, this method returns a regular expression to match them * @param extensions * @return */ private static String buildRE(String extensions){ //String RE="[a-zA-Z0-9-_]+\\.({0})$"; String RE=".+\\.({0})$"; String[] xtns=extensions.split(","); for (int i = 0; i < xtns.length; i++) { String xtn = "("+xtns[i]+")"; if (i<xtns.length-1) xtn+="|{0}"; RE=MessageFormat.format(RE, xtn); } return RE; } /** * Reads the contents of a single file into a useful internal object structure * @param file * @return * @throws FileNotFoundException */ // public static PackageFile extract(File file, CmdArgsParser options) throws FileNotFoundException, UnsupportedEncodingException{ // PackageFile result=new PackageFile(); // result.setFormat(FileIO.getExtension(file)); // result.setBinary(!result.getFormat().endsWith(DRL_FILE_EXTENSION)); // if (!result.isBinary()){ // String content=FileIO.readAll(new FileInputStream(file)); // int packageLoc=content.indexOf(PH_PACKAGE_START); //usually 0 // int ruleLoc=getRuleStart(content, 0);// variable // result.setPackageName(getPackageName(file, options)); //get package name from directory structure // if (ruleLoc<0) return result; // there are no rule's in this file (perhaps functions or other?) // String imports=content.substring(packageLoc, ruleLoc); // result.setImports(imports); // // try { // boolean moreRules = true; // while (moreRules) { // int endLoc = getLoc(content, ruleLoc, RULE_END_MATCHERS)+4; // String ruleContents = content.substring(ruleLoc, endLoc); // ruleLoc = getRuleStart(content, endLoc); // moreRules = ruleLoc >= 0; // Rule rule = new Rule(findRuleName(ruleContents, options), ruleContents); // result.getRules().put(rule.getRuleName(), rule); // } // } catch (StringIndexOutOfBoundsException e) { // System.err.print("Error with file: " + file.getName() + "\n"); // } // }else{ // //binary format (ie. xls) // String content=FileIO.readAllAsBase64(file); // result.setPackageName(getPackageName(file, options)); // result.getRules().put(file.getName(), new Rule(file.getName(), content)); // } // // return result; // } /** * returns "approval.determine" where path is /home/mallen/workspace/rules/approval/determine/1.0.0.SNAPSHOT/file.xls * and options "start" is "rules" and end is "[0-9|.]*[SNAPSHOT]+[0-9|.]*" ie. any number, dot and word SNAPSHOT * @param file * @param options * @return */ private static String getPackageName(File directory, CmdArgsParser options) { String startPath = directory.getPath(); Matcher m = Pattern.compile("([^/]+)").matcher(startPath); List<String> lpath = new ArrayList<String>(); while (m.find()) lpath.add(m.group()); String[] path = lpath.toArray(new String[lpath.size()]); StringBuffer sb = new StringBuffer(); for (int i = path.length - 1; i >= 0; i--) { String dir = path[i]; if ((dir.matches(options.getOption(Parameters.OPTIONS_PACKAGE_EXCLUDE)))) continue; if ((dir.equals(options.getOption(Parameters.OPTIONS_PACKAGE_START)))) break; //since we are working in reverse, it's time to exit sb.insert(0, PACKAGE_DELIMETER).insert(0, dir); } if (sb.substring(sb.length() - 1).equals(PACKAGE_DELIMETER)) sb.delete(sb.length() - 1, sb.length()); return sb.toString(); } /** * Gets the start position of the next rule in the package (<param>contents</param>) * @param contents * @param startLoc * @return */ private static int getRuleStart(String contents, int startLoc){ return getLoc(contents, startLoc, RULE_START_MATCHERS); } private static int getLoc(String contents, int startLoc, String[] markers){ int[] a=new int[markers.length]; for (int i = 0; i < markers.length; i++) { String marker = markers[i]; a[i]=contents.indexOf(marker, startLoc); } //sort int i,j,tmp; for (int x=0;x<a.length;x++){ i = x; for (j=x+1;j<a.length;j++){ if (a[j] < a[i]) i =j; } tmp = a[x]; a[x]=a[i]; a[i]=tmp; } for (int k = 0; k < a.length; k++) { if (a[k]>=0) return a[k]; //return the lowest non-negative number } return -1; } /** * returns the rule name given the entire rule content * @param ruleContents * @return */ private static String findRuleName(String ruleContents, CmdArgsParser options){ //TODO: this is incorrect - what if a rule starts 'rule"rule1"'??? use the getRuleStart method to find the beginning String name=ruleContents.substring(ruleContents.indexOf(PH_RULE_START)+PH_RULE_START.length(), ruleContents.indexOf(PH_NEWLINE)).replaceAll("\"", "").trim(); if (!name.matches("[^'^/^<^>.]+")){ //Guvnor seems to not like some characters if ("true".equals(options.getOption(Parameters.OPTIONS_VERBOSE))) System.out.println("WARNING: fixing invalid rule name [old name="+name+"]"); name=name.replaceAll("'", ""); //remove all ' chars since they are not valid in rule names name=name.replaceAll("/", "-"); //remove all / chars since they are not valid in rule names name=name.replaceAll("<", "<"); //remove all < chars since they are not valid in rule names name=name.replaceAll(">", ">"); //remove all > chars since they are not valid in rule names } return name; } // GETTERS/SETTERS for PackageFile object public boolean isFormat(Format isFormat){ if (ruleFiles!=null && ruleFiles.size()>0){ String name=ruleFiles.get(0).getName().toLowerCase(); return name.endsWith(isFormat.value); } return false; } public String getFormat() { if (ruleFiles!=null && ruleFiles.size()>0){ String name=ruleFiles.get(0).getName().toLowerCase(); if (name.endsWith("drl")){ return "drl"; }else if (name.endsWith("xls")){ return "xls"; } } return ""; } // public void setFormat(String format) { // this.format = format; // } public File getFile() { return file; } public void setFile(File file) { this.file = file; } public String getDependencyErrors() { return dependencyErrors; } public void setDependencyErrors(String dependencyErrors) { this.dependencyErrors = dependencyErrors; } public boolean hasDependencyErrors() { return dependencyErrors.length()>0; } public void addDependencyError(String dependencyError) { this.dependencyErrors+=dependencyError+"\n"; } public String getCompilationErrors() { return compilationErrors; } public void setCompilationErrors(String compilationErrors) { this.compilationErrors = compilationErrors; } public boolean hasCompilationErrors() { return compilationErrors.length()>0; } public void addCompilationError(String compilationError) { this.compilationErrors+=compilationError+"\n"; } public boolean hasErrors(){ return hasCompilationErrors() || hasDependencyErrors(); } public Package getPkg() { return pkg; } public void setPkg(Package pkg) { this.pkg = pkg; } public String getImports() { return imports; } public void addImports(String imports) { //strip out any "package " lines from additional imports StringBuffer sb=new StringBuffer(imports); if (imports.length()>0){ int posPackage=imports.indexOf("package "); sb.delete(posPackage, imports.indexOf("\n", posPackage)); } this.imports=new StringBuffer().append(imports).append("\n").append(sb).toString(); } public Map<String, Rule> getRules() { return rules; } public void setRules(Map<String, Rule> rules) { this.rules = rules; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString(){ return "PackageFile[name="+name+",format="+getFormat()+"]"; } }