/*
* Copyright 2008 Fedora Commons, 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.mulgara.krule.rlog;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import org.apache.log4j.Logger;
import org.mulgara.krule.rlog.ast.*;
import org.mulgara.krule.rlog.parser.TypeException;
import org.mulgara.krule.rlog.parser.URIParseException;
import org.mulgara.krule.rlog.rdf.MulgaraGraphs;
import org.mulgara.krule.rlog.rdf.URIReference;
import org.mulgara.krule.rlog.rdf.Var;
/**
* This class is used to interpret a string into an AST. The AST is then transformed into a krule encoding in XML.
*
* @created Feb 22, 2009
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
*/
public class Rlog implements Interpreter {
/** Logger. */
private static final Logger logger = Logger.getLogger(Rlog.class.getName());
/** The label used on all generated rules. */
private static String ruleLabel = "rule";
/** All the parsed statements to be processed. */
private List<Statement> statements;
/** All the parsed Axioms. */
private List<Axiom> axioms;
/** All the parsed Rules. */
private List<Rule> rules;
/**
* Sets the label to use for each rule. The rule names are made up of this label
* plus an incremening number.
* @param label The text label to use on rules.
*/
public static void setRuleLabel(String label) {
ruleLabel = label;
}
/**
* Create an rlog interpreter for building an AST from a stream Reader object.
* @param input The stream Reader.
* @param inputLocation The location the input comes from.
* @throws IOException There was an IO Exception on the input.
* @throws beaver.Parser.Exception There was a parser exception in the input data.
* @throws URIParseException If the rules contain illegal URIs.
*/
public Rlog(Reader input, URI inputLocation) throws IOException, ParseException, TypeException, URIParseException {
Program program = loadProgram(input, inputLocation);
statements = program.getStatements();
// separate out the rules from the axioms
int ruleCount = 0;
rules = new ArrayList<Rule>();
axioms = new ArrayList<Axiom>();
for (Statement s: statements) {
if (s instanceof Axiom) axioms.add((Axiom)s);
else if (s instanceof Rule) {
rules.add((Rule)s);
((Rule)s).setName(ruleLabel + ++ruleCount);
} else throw new IllegalStateException("Unknown statement type found: " + s.getClass().getName());
}
// calculate dependencies between the rules
calculateRuleDependencies();
}
/**
* Loads a program from an input Reader.
* @param input The Reader to get the program from.
* @param baseLocation The location context to load the program in. This is used for relative
* import declartations.
* @return The fully loaded program, including imports.
* @throws IOException Due to an IO error while loading the program.
* @throws ParseException If the program contains an error.
*/
private Program loadProgram(Reader input, URI baseLocation) throws IOException, ParseException {
// parse the rlog into statements
RlogParser parser = new RlogParser(input);
try {
Program program = parser.getProgram();
loadImports(program, baseLocation);
return program;
} catch (ParseException e) {
logger.error("Error parsing program: " + e.getMessage());
throw e;
} catch (IOException e) {
logger.error("IO Error reading program: " + e.getMessage());
throw e;
}
}
/**
* Find all the variables in every rule.
* @return A complete collection of all the variables that were parsed.
*/
public Collection<Var> getVariables() {
LinkedHashSet<Var> vars = new LinkedHashSet<Var>();
for (Rule r: rules) vars.addAll(r.getVariables());
return vars;
}
/**
* Gets all the URIs referenced in the rules.
* @return All URIs in order of appearance within axioms, then rules.
* @throws URIParseException The referenced URIs had bad syntax.
*/
public Set<URIReference> getReferences() throws URIParseException {
Set<URIReference> refs = new LinkedHashSet<URIReference>();
for (Axiom a: axioms) refs.addAll(a.getReferences());
for (Rule r: rules) refs.addAll(r.getReferences());
refs.addAll(MulgaraGraphs.getSpecialUriRefs());
return refs;
}
/**
* Get all the axioms appearing in the rule set.
* @return A list of axioms.
*/
public List<Axiom> getAxioms() {
return Collections.unmodifiableList(axioms);
}
/**
* Get all the rules appearing in the rule set.
* @return A list of rules.
*/
public List<Rule> getRules() {
return Collections.unmodifiableList(rules);
}
/**
* Determine which rules are dependent on the result of which other rules,
* and set the rule objects accordingly.
* @throws URIParseException If the rules contain illegal URIs.
*/
private void calculateRuleDependencies() throws TypeException, URIParseException {
for (Rule trigger: rules) {
for (Rule potentialTarget: rules) {
if (potentialTarget.triggeredBy(trigger)) trigger.addTrigger(potentialTarget);
}
}
}
/**
* Load all imports into the given program.
* @param prog The program to load its imports into.
* @param currentLocation A URI for the location of the program. Used for relative loads.
* @throws IOException If the import files cannot be read.
* @throws ParseException If an imported program has an error.
*/
private void loadImports(Program prog, URI currentLocation) throws IOException, ParseException {
List<URI> imports = prog.getImports();
for (URI imp: imports) {
URL importLocation = makeAbsolute(currentLocation, imp);
if (logger.isDebugEnabled()) logger.debug("Importing " + importLocation);
Reader input = new InputStreamReader(importLocation.openStream());
Program impProgram;
try {
impProgram = loadProgram(input, currentLocation);
} catch (ParseException e) {
logger.error("Error in imported program <" + importLocation + ">");
throw e;
} finally {
input.close();
}
prog.merge(impProgram);
}
}
/**
* Calculate a URL from a given URI, relative to the current base location.
* @param base The base to calculate the final URL from. Must be a URL if <var>rel</var> is relative.
* @param rel The URL that is relative to the base. If absolute, then <var>base</var> is ignored.
* @return A new URL, which is <var>rel</var> if it is absolute, or a merge of <var>rel</var> and
* <var>base</var> if <var>rel</var> is relative.
* @throws IllegalArgumentException If <var>rel</var> is relative and <var>base</var> is not a URL,
* or if <var>rel</var> (or <var>base</var>+<var>rel</var>) is not a valid URL.
*/
private URL makeAbsolute(URI base, URI rel) {
try {
if (!rel.isAbsolute()) {
if (base == null) throw new IllegalArgumentException("Relative URL used in import with no current location set");
return new URL(base.toURL(), rel.toString());
} else {
return rel.toURL();
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL for import of (" + rel + ")" + ": "+ e.getMessage());
}
}
}