Java tutorial
/* * 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.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.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; 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.FileIOHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a drl package file found in the file system */ public class PackageFile implements Comparator<Integer> { 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 String imports = ""; //default to no imports private String dependencyErrors = ""; private String compilationErrors = ""; private Map<String, Rule> rules = new HashMap<String, Rule>(); private Map<String, File> ruleFiles = new HashMap<String, File>(); private String name; private String modelContent; private List<Model> modelFiles = new ArrayList<Model>(); private enum Format { DRL(".drl"), XLS(".xls"); String value; Format(String value) { this.value = value; } } public List<Model> getModelFiles() { return modelFiles; } public void setModelFiles(File[] files) throws UnsupportedEncodingException { for (File modelFile : files) { if (!modelFile.exists()) { throw new RuntimeException("model file does not exist [" + modelFile.getAbsolutePath() + "]"); } modelContent = FileIOHelper.readAllAsBase64(modelFile); modelFiles.add(new Model(modelFile, modelContent)); } } /** * 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 IOException { 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 IllegalStateException("path (" + path + ") must be a directory"); } buildPackageForDirectory(result, location, options); return result; } public boolean containsAssets() { return this.modelFiles.size() > 0 || this.ruleFiles.size() > 0; } /** * 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 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); File[] modelFiles = getJarFiles(files[i]); PackageFile packageFile = new PackageFile(); packageFile.setName(getPackageName(files[i], options)); if (modelFiles.length > 0) { packageFile.setModelFiles(modelFiles); } if (ruleFiles.length > 0) { packageFile = parseRuleFiles(packageFile, ruleFiles, options); } if (packageFile.containsAssets()) packages.put(packageFile.getName(), packageFile); if (recurse) buildPackageForDirectory(packages, files[i], options); } } } private static File[] getJarFiles(File directory) { return directory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return !name.startsWith(".") && name.endsWith(".jar"); } }); } 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 (File file : files) { if (file.isFile()) result.add(file); } return result.toArray(new File[result.size()]); } return new File[] {}; } private static PackageFile parseRuleFiles(PackageFile result, File[] ruleFiles, CmdArgsParser options) throws IOException { for (int i = 0; i < ruleFiles.length; i++) { File file = ruleFiles[i]; if (file.getName().endsWith(".drl")) { parseDrlFile(file, result, options); } else if (file.getName().endsWith(".xls")) { parseXlsFile(file, result, options); } } return result; } private static void parseXlsFile(File file, PackageFile packageFile, CmdArgsParser options) throws FileNotFoundException, UnsupportedEncodingException { String content = FileIOHelper.readAllAsBase64(file); packageFile.getRules().put(file.getName(), new Rule(file.getName(), content, file)); packageFile.getRuleFiles().put(file.getName(), file); } class ImportsComparator implements Comparator<String> { public int compare(String o1, String o2) { if (!o1.contains("package") && o2.contains("package")) { return 1; } return 0; } } private void cleanImports() { List<String> list = new ArrayList<String>(); Set<String> set = new HashSet<String>(); // separate out import and package lines StringTokenizer st = new StringTokenizer(getImports(), "\n"); while (st.hasMoreTokens()) { list.add(st.nextToken().trim()); } // ensure each line is unique set.addAll(list); list.clear(); list.addAll(set); // sort so that package is at the top Collections.sort(list, new ImportsComparator()); // push back into the package imports StringBuffer sb = new StringBuffer(); for (String line : list) { sb.append(line).append("\n"); } imports = sb.toString(); } private static void parseDrlFile(File file, PackageFile packageFile, CmdArgsParser options) throws FileNotFoundException { String content; try { content = FileUtils.readFileToString(file); } catch (IOException e) { throw new IllegalArgumentException("Error reading file (" + file + ")", e); } 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); packageFile.cleanImports(); 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, file); packageFile.getRules().put(rule.getRuleName(), rule); packageFile.getRuleFiles().put(rule.getRuleName(), file); } } 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 */ public void buildPackage() throws IOException { PackageBuilder pb = new PackageBuilder(); for (String key : getRuleFiles().keySet()) { File file = getRuleFiles().get(key); try { if (FUNCTIONS_FILE != null) { File functionsFile = new File(file.getParentFile().getPath(), FUNCTIONS_FILE); if (functionsFile.exists()) { pb.addPackageFromDrl(new FileReader(functionsFile)); } } if (file.getName().endsWith(Format.DRL.value)) { pb.addPackageFromDrl(new FileReader(file)); } else if (file.getName().endsWith(Format.XLS.value)) { pb.addPackageFromDrl(new StringReader(DroolsHelper.compileDTabletoDRL(file, InputType.XLS))); } } catch (DroolsParserException e) { throw new IllegalArgumentException("File (" + file + ") throw a DroolsParserException.", e); } } 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 (String key : getRuleFiles().keySet()) { File file = getRuleFiles().get(key); 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[] {}; } } public void addRuleFile(String ruleName, File ruleFile) { ruleFiles.put(ruleName, ruleFile); } public Map<String, File> getRuleFiles() { return ruleFiles; } /** * 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; } /** * 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 directory * @param options * @return */ private static String getPackageName(File directory, CmdArgsParser options) { String startPath = directory.getPath(); Matcher m = Pattern.compile("([^/|\\\\]+)").matcher(startPath); // quad-backslash is for windows paths 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) { List<Integer> a = new ArrayList<Integer>(); for (int i = 0; i < markers.length; i++) { a.add(Integer.valueOf(contents.indexOf(markers[i], startLoc))); } Collections.sort(a, new PackageFile()); for (int k = 0; k < a.size(); k++) { if (a.get(k) >= 0) return a.get(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 Logger logger = LoggerFactory.getLogger(PackageFile.class); logger.debug("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 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 + "]"; } public int compare(Integer o1, Integer o2) { return o1 - o2; } }