// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.prelude.semantics; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; import java.util.List; import com.yahoo.io.IOUtils; import com.yahoo.io.reader.NamedReader; import com.yahoo.prelude.semantics.parser.*; /** * Imports rule bases from various sources. * * @author bratseth */ // Uses the JavaCC-generated parser to read rule bases. // This is an intermediate between the parser and the rule base being loaded // on implementation of some directives, for example, it knows where to find // rule bases included into others, while neither the rule base or the parser knows. public class RuleImporter { /** * If this is set, imported rule bases are looked up in this config * otherwise, they are looked up as files */ private SemanticRulesConfig config; /** * Ignore requests to read automata files. * Useful to validate rule bases without having automatas present */ private boolean ignoreAutomatas; /** * Ignore requests to include files. * Useful to validate rule bases one by one in config */ private boolean ignoreIncludes = false; /** Create a rule importer which will read from file */ public RuleImporter() { this(null, false); } /** Create a rule importer which will read from a config object */ public RuleImporter(SemanticRulesConfig config) { this(config, false); } public RuleImporter(boolean ignoreAutomatas) { this(null, ignoreAutomatas); } public RuleImporter(boolean ignoreAutomatas, boolean ignoreIncludes) { this(null, ignoreAutomatas, ignoreIncludes); } public RuleImporter(SemanticRulesConfig config, boolean ignoreAutomatas) { this.config = config; this.ignoreAutomatas = ignoreAutomatas; } public RuleImporter(SemanticRulesConfig config, boolean ignoreAutomatas, boolean ignoreIncludes) { this.config = config; this.ignoreAutomatas = ignoreAutomatas; this.ignoreIncludes = ignoreIncludes; } /** * Imports semantic rules from a file * * @param fileName the rule file to use * @throws java.io.IOException if the file can not be read for some reason * @throws ParseException if the file does not contain a valid semantic rule set */ public RuleBase importFile(String fileName) throws IOException, ParseException { return importFile(fileName, null); } /** * Imports semantic rules from a file * * @param fileName the rule file to use * @param automataFile the automata file to use, or null to not use any * @throws java.io.IOException if the file can not be read for some reason * @throws ParseException if the file does not contain a valid semantic rule set */ public RuleBase importFile(String fileName, String automataFile) throws IOException, ParseException { return importFile(fileName, automataFile, null); } /** * Imports semantic rules from a file * * @param fileName the rule file to use * @param automataFile the automata file to use, or null to not use any * @param ruleBase an existing rule base to import these rules into, or null to create a new * @throws java.io.IOException if the file can not be read for some reason * @throws ParseException if the file does not contain a valid semantic rule set */ public RuleBase importFile(String fileName, String automataFile, RuleBase ruleBase) throws IOException, ParseException { ruleBase = privateImportFile(fileName, automataFile, ruleBase); ruleBase.initialize(); return ruleBase; } public RuleBase privateImportFile(String fileName, String automataFile, RuleBase ruleBase) throws IOException, ParseException { BufferedReader reader = null; try { reader = IOUtils.createReader(fileName, "utf-8"); File file = new File(fileName); String absoluteFileName = file.getAbsolutePath(); if (ruleBase == null) ruleBase = new RuleBase(); ruleBase.setName(stripLastName(file.getName())); privateImportFromReader(reader, absoluteFileName, automataFile, ruleBase); return ruleBase; } finally { IOUtils.closeReader(reader); } } /** Imports all the rule files (files ending by "sr") in the given directory */ public List importDir(String ruleBaseDir) throws IOException, ParseException { File ruleBaseDirFile = new File(ruleBaseDir); if ( ! ruleBaseDirFile.exists()) throw new IOException("Rule base dir '" + ruleBaseDirFile.getAbsolutePath() + "' does not exist"); File[] files = ruleBaseDirFile.listFiles(); Arrays.sort(files); List ruleBases = new java.util.ArrayList<>(); for (File file : files) { if ( ! file.getName().endsWith(".sr")) continue; RuleBase base = importFile(file.getAbsolutePath()); ruleBases.add(base); } return ruleBases; } /** Read and include a rule base in another */ public void include(String ruleBaseName, RuleBase ruleBase) throws java.io.IOException, ParseException { if (ignoreIncludes) return; RuleBase include; if (config == null) { include = privateImportFromDirectory(ruleBaseName, ruleBase); } else { include = privateImportFromConfig(ruleBaseName); } ruleBase.include(include); } /** Returns an unitialized rule base */ private RuleBase privateImportFromDirectory(String ruleBaseName, RuleBase ruleBase) throws IOException, ParseException { RuleBase include = new RuleBase(); String includeDir = new File(ruleBase.getSource()).getParentFile().getAbsolutePath(); if (!ruleBaseName.endsWith(".sr")) ruleBaseName = ruleBaseName + ".sr"; File importFile = new File(includeDir, ruleBaseName); if ( ! importFile.exists()) throw new IOException("No file named '" + shortenPath(importFile.getPath()) + "'"); return privateImportFile(importFile.getPath(), null, include); } /** Returns an unitialized rule base */ private RuleBase privateImportFromConfig(String ruleBaseName) throws IOException, ParseException { SemanticRulesConfig.Rulebase ruleBaseConfig = findRuleBaseConfig(config,ruleBaseName); if (ruleBaseConfig == null) ruleBaseConfig = findRuleBaseConfig(config, stripLastName(ruleBaseName)); if (ruleBaseConfig == null) throw new ParseException("Could not find included rule base '" + ruleBaseName + "'"); return privateImportConfig(ruleBaseConfig); } private SemanticRulesConfig.Rulebase findRuleBaseConfig(SemanticRulesConfig config, String ruleBaseName) { for (Object aRulebase : config.rulebase()) { SemanticRulesConfig.Rulebase ruleBaseConfig = (SemanticRulesConfig.Rulebase)aRulebase; if (ruleBaseConfig.name().equals(ruleBaseName)) return ruleBaseConfig; } return null; } public void setAutomata(RuleBase base, String automata) { if (ignoreAutomatas) base.setUsesAutomata(true); // Stop it from failing on automata condition references else base.setAutomataFile(automata); } static String stripLastName(String fileName) { int lastDotIndex = fileName.lastIndexOf("."); if (lastDotIndex < 0) return fileName; return fileName.substring(0, lastDotIndex); } public RuleBase importString(String string, String automataFile) throws IOException, ParseException { return importString(string, automataFile, null, null); } public RuleBase importString(String string, String automataFile, String sourceName) throws IOException, ParseException { return importString(string, automataFile, sourceName, null); } public RuleBase importString(String string, String automataFile, RuleBase ruleBase) throws IOException, ParseException { return importString(string, automataFile, null, ruleBase); } public RuleBase importString(String string, String automataFile, String sourceName, RuleBase ruleBase) throws IOException, ParseException { return importFromReader(new StringReader(string), sourceName, automataFile, ruleBase); } public RuleBase importConfig(SemanticRulesConfig.Rulebase ruleBaseConfig) throws IOException, ParseException { RuleBase ruleBase = privateImportConfig(ruleBaseConfig); ruleBase.initialize(); return ruleBase; } /** Imports an unitialized rule base */ public RuleBase privateImportConfig(SemanticRulesConfig.Rulebase ruleBaseConfig) throws ParseException { if (config == null) throw new IllegalStateException("Must initialize with config if importing from config"); RuleBase ruleBase = new RuleBase(); ruleBase.setName(ruleBaseConfig.name()); return privateImportFromReader(new StringReader(ruleBaseConfig.rules()), "semantic-rules.cfg", ruleBaseConfig.automata(),ruleBase); } public RuleBase importFromReader(Reader reader, String sourceInfo, String automataFile) throws ParseException { return importFromReader(reader, sourceInfo, automataFile, null); } /** * Imports rules from a reader * * @param reader the reader containing rules on the proper syntax * @param sourceName a string describing the source of the rules used for error messages * @param ruleBase an existing rule base to import the rules into, or null to create a new one * @return the rule base containing the rules added from the reader * @throws ParseException if the reader contains illegal rule syntax */ public RuleBase importFromReader(Reader reader, String sourceName, String automataFile, RuleBase ruleBase) throws ParseException { ruleBase = privateImportFromReader(reader, sourceName, automataFile, ruleBase); ruleBase.initialize(); return ruleBase; } /** Returns an unitialized rule base */ public RuleBase privateImportFromReader(Reader reader, String sourceName, String automataFile, RuleBase ruleBase) throws ParseException { try { if (ruleBase == null) { ruleBase = new RuleBase(); if (sourceName == null) sourceName = "anonymous"; ruleBase.setName(sourceName); } ruleBase.setSource(sourceName.replace('\\', '/')); new SemanticsParser(reader).semanticRules(ruleBase, this); if (automataFile != null && !automataFile.isEmpty()) ruleBase.setAutomataFile(automataFile.replace('\\', '/')); return ruleBase; } catch (Throwable t) { // also catches token mgr errors ParseException p = new ParseException("Could not parse '" + shortenPath(sourceName) + "'"); p.initCause(t); throw p; } } /** * Snips what's in from of rules/ if "rules/" is present in the string * to avoid displaying details about where application content is copied * (if rules/ is present, these rules are read from an applicatino package) */ private static String shortenPath(String path) { int rulesIndex = path.indexOf("rules/"); if (rulesIndex < 0) return path; return path.substring(rulesIndex); } }