aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/prelude/semantics/RuleImporter.java
blob: 45569050882791ba0da23ede689e2ec31377258b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// 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<RuleBase> 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<RuleBase> 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);
    }

}