blob: 6596cec104f074c73e18ee560e618f40fb9655c2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.uima.ruta.block.fst;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.ruta.RutaStatement;
import org.apache.uima.ruta.RutaStream;
import org.apache.uima.ruta.block.RutaBlock;
import org.apache.uima.ruta.condition.AbstractRutaCondition;
import org.apache.uima.ruta.rule.ComposedRuleElementMatch;
import org.apache.uima.ruta.rule.EvaluatedCondition;
import org.apache.uima.ruta.rule.MatchContext;
import org.apache.uima.ruta.rule.RuleElement;
import org.apache.uima.ruta.rule.RuleElementMatch;
import org.apache.uima.ruta.rule.RuleMatch;
import org.apache.uima.ruta.rule.RutaMatcher;
import org.apache.uima.ruta.rule.RutaRule;
import org.apache.uima.ruta.rule.RutaRuleElement;
import org.apache.uima.ruta.verbalize.RutaVerbalizer;
import org.apache.uima.ruta.visitor.InferenceCrowd;
public class Automaton {
private RutaVerbalizer verbalizer;
private RootState root;
/**
* Constructor that builds the FST from the Rules in rulesList
*
* @param rulesList
* - the rules to build the FST from
*/
public Automaton(List<RutaStatement> rulesList) {
this.verbalizer = new RutaVerbalizer();
this.root = this.buildAutomaton(rulesList);
}
/**
* Builds the FST from the Rules in rulesList.
*
* @param rulesList
* - the rules to build the FST from
* @return the RootState for the built FST
*/
public RootState buildAutomaton(List<RutaStatement> rulesList) {
RootState root = new RootState();
for (RutaStatement statement : rulesList) {
AbstractState startState = root;
if (statement instanceof RutaRule) {
RutaRule rule = (RutaRule) statement;
int depth = 1;
for (RuleElement element : rule.getRuleElements()) {
AbstractState targetState = null;
for (RuleElement toCompare : startState.getPossibleTransitions()) {
if (ruleElementEquals(element, toCompare)) {
targetState = startState.getTransition(toCompare);
}
if (targetState instanceof EndState) {
// Falls zwei Regeln genau gleich, erstelle trotzdem
// zwei verschiedene Endzustände
targetState = null;
}
}
if (targetState == null) { // Erstelle neuen Zielzustand
if (depth == rule.getRuleElements().size()) {
targetState = new EndState(statement, depth);
} else {
targetState = new TransitionState(depth);
((TransitionState) targetState).addRule(statement);
}
startState.addTransition(element, targetState);
} else { // Benutze existierenden Zielzustand
if (targetState instanceof TransitionState) {
((TransitionState) targetState).addRule(statement);
}
}
startState = targetState;
depth++;
}
}
}
return root;
}
/**
* Compares two RuleElements. They are if their verbalization, e. g. "CW" or "SW" is equal, not if
* elem1 == elem2 or elem1.equals(elem2)
*
* @param elem1
* - The first RuleElement to compare
* @param elem2
* - The second RuleElement to compare
*
* @return true if the verbalization of the two RuleElements equals, else false
*/
public boolean ruleElementEquals(RuleElement elem1, RuleElement elem2) {
return verbalizer.verbalize(elem1).equals(verbalizer.verbalize(elem2));
}
/**
* Starts the execution of the Automaton in the rootState
*
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
* @param parent
* - the RutaBlock (is needed in called functions)
*/
public void apply(RutaStream stream, InferenceCrowd crowd, RutaBlock parent) {
for (RuleElement element : root.getPossibleTransitions()) {
AbstractState targetState = root.getTransition(element);
RutaMatcher matcher = ((RutaRuleElement) element).getMatcher();
for (AnnotationFS annoFS : matcher.getMatchingAnnotations(parent, stream)) {
if (targetState instanceof TransitionState) {
LinkedList<RuleMatch> ruleMatches = createMatches(annoFS,
((TransitionState) targetState).getRules(), stream, crowd);
doTransition((TransitionState) targetState, annoFS, element, ruleMatches, stream, crowd,
parent);
} else {
RuleMatch ruleMatch = createMatch(annoFS, (RutaRule) ((EndState) targetState).getRule(),
stream, crowd);
addAnnotation((EndState) targetState, ruleMatch, stream, crowd);
}
}
}
}
/**
* Pursues the execution of the Automaton in the next state.
*
* @param startState
* - the state to go on from
* @param anno
* - the matched Annotation from the previous transition
* @param ruleElement
* - the last matched RuleElement
* @param matches
* - the list of RuleMatches corresponding to the rules in the state
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
* @param parent
* - the RutaBlock (is needed in called functions)
*/
private void doTransition(TransitionState startState, AnnotationFS anno, RuleElement ruleElement,
LinkedList<RuleMatch> matches, RutaStream stream, InferenceCrowd crowd,
RutaBlock parent) {
for (RuleElement element : startState.getPossibleTransitions()) {
RutaMatcher matcher = ((RutaRuleElement) element).getMatcher();
AbstractState targetState = startState.getTransition(element);
for (AnnotationFS annoFS : matcher.getAnnotationsAfter((RutaRuleElement) ruleElement, anno,
parent, stream)) {
if (targetState instanceof TransitionState) {
LinkedList<RuleMatch> ruleMatches = filterMatches(annoFS, matches,
(TransitionState) targetState, stream, crowd);
doTransition((TransitionState) targetState, annoFS, element, ruleMatches, stream, crowd,
parent);
} else {
RuleMatch ruleMatch = filterMatch(annoFS, matches, (EndState) targetState, stream, crowd);
addAnnotation((EndState) targetState, ruleMatch, stream, crowd);
}
}
}
}
/**
* Creates the Annotation after a full matching of a rule, this means the FST reached an EndState
*
*
* @param targetState
* - the reached Endstate representing the rule
* @param ruleMatch
* - the RuleMatch corresponding the Rule
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*/
public void addAnnotation(EndState targetState, RuleMatch ruleMatch, RutaStream stream,
InferenceCrowd crowd) {
RutaStatement statement = targetState.getRule();
if (statement instanceof RutaRule) {
RutaRule rule = (RutaRule) statement;
rule.getRoot().apply(ruleMatch, stream, crowd);
}
}
/**
* Checks the matching of the annotation and updates the RuleMatch
*
* @param annotation
* - the matched Annotation
* @param ruleMatch
* - the RuleMatch to update
* @param element
* - the matched RuleElement
* @param containerMatch
* - the ComposedRuleElement of the rule containing element
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*/
private void doMatch(AnnotationFS annotation, RuleMatch ruleMatch, RuleElement element,
ComposedRuleElementMatch containerMatch, RutaStream stream, InferenceCrowd crowd) {
RuleElementMatch result = new RuleElementMatch(element, containerMatch);
List<EvaluatedCondition> evaluatedConditions = new ArrayList<EvaluatedCondition>(
element.getConditions().size());
// boolean base = matcher.match(annotation, stream, getParent());
boolean base = true;
MatchContext context = new MatchContext(annotation, element, ruleMatch, true);
List<AnnotationFS> textsMatched = new ArrayList<AnnotationFS>(1);
if (annotation != null) {
textsMatched.add(annotation);
}
result.setMatchInfo(base, textsMatched, stream);
if (base) {
for (AbstractRutaCondition condition : element.getConditions()) {
crowd.beginVisit(condition, null);
EvaluatedCondition eval = condition.eval(context, stream, crowd);
crowd.endVisit(condition, null);
evaluatedConditions.add(eval);
}
}
if (annotation != null) {
textsMatched.add(annotation);
}
result.setConditionInfo(base, evaluatedConditions);
ruleMatch.setMatched(ruleMatch.matched() && result.matched());
}
/**
* Creates a list of RuleMatches with one RuleMatch with the annotation for every rule for a
* transition from the RootState into a TransitionState
*
* @param annoFS
* - the matched annotation
* @param rulesList
* - the list of rules
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*
* @return - a list with the created RuleMatches
*/
private LinkedList<RuleMatch> createMatches(AnnotationFS annoFS, List<RutaStatement> rulesList,
RutaStream stream, InferenceCrowd crowd) {
// RootState -> TransitionState
LinkedList<RuleMatch> ruleMatches = new LinkedList<RuleMatch>();
for (RutaStatement statement : rulesList) {
RutaRule rule = (RutaRule) statement;
RuleElement element = rule.getRuleElements().get(0);
RuleMatch match = new RuleMatch(rule);
ComposedRuleElementMatch rootMatch = new ComposedRuleElementMatch(rule.getRoot(), null);
match.setRootMatch(rootMatch);
doMatch(annoFS, match, element, rootMatch, stream, crowd);
ruleMatches.add(match);
}
return ruleMatches;
}
/**
* Creates the match for the transition from the RootState directly into an EndState (happens if
* the rule only has one RuleElement).
*
* @param annoFS
* - the matches annotation for the RuleMatch
* @param rule
* - the matched rule for the RuleMatch
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*
* @return the created RuleMatch for the rule with the matched annotation
*/
private RuleMatch createMatch(AnnotationFS annoFS, RutaRule rule, RutaStream stream,
InferenceCrowd crowd) {
// RootState -> EndState
RuleElement element = rule.getRuleElements().get(0);
RuleMatch match = new RuleMatch(rule);
ComposedRuleElementMatch rootMatch = new ComposedRuleElementMatch(rule.getRoot(), null);
match.setRootMatch(rootMatch);
doMatch(annoFS, match, element, rootMatch, stream, crowd);
return match;
}
/**
* Filters the rules for a transition from a TransitionState into a TransitionState and adds an
* InnerMatch for the matched Annotationen to the corresponding RuleMatches
*
* @param annoFS
* - the matched Annotation
* @param ruleMatches
* - the RuleMatches which are filtered
* @param targetState
* - the TransitionState for which the RuleMatches are filtered
* @param stream
* - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*
* @return the RuleMatches for the Rules which are represented by the TransitionState targetState
*/
private LinkedList<RuleMatch> filterMatches(AnnotationFS annoFS,
LinkedList<RuleMatch> ruleMatches, TransitionState targetState, RutaStream stream,
InferenceCrowd crowd) {
// TransitionState -> TransitionState
LinkedList<RuleMatch> retList = new LinkedList<RuleMatch>();
for (RuleMatch match : ruleMatches) {
for (RutaStatement statement : targetState.getRules()) {
RutaRule rule = (RutaRule) statement;
if (match.getRule().equals(rule)) {
RuleElement element = rule.getRuleElements().get(targetState.getDepth() - 1);
doMatch(annoFS, match, element, match.getRootMatch(), stream, crowd);
retList.add(match);
}
}
}
return retList;
}
/**
* Filters the one RuleMatch from the list of RuleMatches, which corresponds to the Rule from the
* EndState state and adds the Annotation to the list of matched Annotation of this RuleMatch
*
* @param annoFS
* - the Annotation to add
* @param ruleMatches
* - the list of RuleMatches to filter the single RuleMatch from
* @param state
* - the state the rule gets taken from
* @param stream
* stream - the RutaStream (is needed in the called functions)
* @param crowd
* - the InferenceCrowd (is needed in the called functions)
*
* @return the single RuleMatch corresponding to the rule from the state
*/
private RuleMatch filterMatch(AnnotationFS annoFS, LinkedList<RuleMatch> ruleMatches,
EndState state, RutaStream stream, InferenceCrowd crowd) {
// TransitionState -> EndState
RutaRule rule = (RutaRule) state.getRule();
for (RuleMatch match : ruleMatches) {
if (rule.equals(match.getRule())) {
RuleElement element = rule.getRuleElements().get(state.getDepth() - 1);
doMatch(annoFS, match, element, match.getRootMatch(), stream, crowd);
return match;
}
}
// Should not happen!
return ruleMatches.get(0);
}
}