package aimax.osm.data; import java.util.ArrayList; import java.util.List; import aimax.osm.data.entities.MapEntity; /** * Classifies map entities with respect to their attributes based * on declarative classification rules. A classifier consists of a * default classification result and a sequence of classification rules. * * <p>Logically, a rule consists of a condition, which constrains name and * (optionally) value of one of the entity's attributes, and a sub-classifier. * Sub-classifiers should provide a default classification result. * Additionally, they can provide further rules which check other * attributes. When classifying an entity, the rules are * checked in the given order and the classifier of the first match is * applied recursively. If no rule match is found, the classifier's default * classification result is returned.</p> * * <p>For efficiency reasons, rules are grouped by attribute * name on implementation level, and within such a group, binary search is * used to find a match.</p> * @author Ruediger Lunde * @param <C> Class of classification results. */ public class EntityClassifier<C> { List<RuleGroup<C>> rules; C defaultEntityClass; /** Default constructor. */ public EntityClassifier() { rules = new ArrayList<RuleGroup<C>>(); } public C getDefaultEntityClass() { return defaultEntityClass; } public void setDefaultEntityClass(C defaultEntityClass) { this.defaultEntityClass = defaultEntityClass; } /** Clears the default classification result and all rules. */ public void clear() { rules.clear(); defaultEntityClass = null; } /** * Adds a new classification rule. * @param attName Name of an attribute. * @param attValue Value of an attribute or null. * @param eclass Default classification result, possibly null. * @return The sub-classifier corresponding to the rule. */ public EntityClassifier<C> addRule(String attName, String attValue, C eclass) { EntityClassifier<C> result = new EntityClassifier<C>(); result.setDefaultEntityClass(eclass); RuleGroup<C> rg = null; if (!rules.isEmpty()) { RuleGroup<C> last = rules.get(rules.size()-1); if (last.attName.equals(attName)) rg = last; } if (rg == null) { rg = new RuleGroup<C>(attName); rules.add(rg); } if (attValue == null) rg.defaultSubClassifier = result; else { int i = 0; while (i < rg.attValueRules.size() && attValue.compareTo(rg.attValueRules.get(i).attValue)>0) i++; rg.attValueRules.add(i, new Rule<C>(attValue, result)); } return result; } /** * Replaces an existing rule and returns the corresponding * sub-classifier if found. */ public EntityClassifier<C> replaceRule(String attName, String attValue, C eclass) { EntityClassifier<C> newClassifier = new EntityClassifier<C>(); newClassifier.setDefaultEntityClass(eclass); for (RuleGroup<C> rg : rules) { if (attName.equals(rg.attName)) { if (attValue == null) { if (rg.defaultSubClassifier != null) { rg.defaultSubClassifier = newClassifier; return newClassifier; } } else { for (Rule<C> rule : rg.attValueRules) { if (attValue.equals(rule.attValue)) { rule.subClassifier = newClassifier; return newClassifier; } } } } } return null; } /** Classifies a map entity with respect to the given rules. */ public C classify(MapEntity entity) { C result = null; for (RuleGroup<C> rg : rules) { String attValue = entity.getAttributeValue(rg.attName); if (attValue != null) { int min = 0; int max = rg.attValueRules.size()-1; int curr; int cr; Rule<C> currRule; while (min <= max) { curr = (min+max)/2; currRule = rg.attValueRules.get(curr); cr = attValue.compareTo(currRule.attValue); if (cr < 0) max = curr-1; else if (cr > 0) min = curr+1; else { result = currRule.subClassifier.classify(entity); break; } } if (result == null && rg.defaultSubClassifier != null) { result = rg.defaultSubClassifier.classify(entity); } if (result != null) return result; } } return defaultEntityClass; } ///////////////////////////////////////////////////////////////// // inner classes /** Maintains a sub-classifier for one attribute value. */ private static class Rule<C> { String attValue; EntityClassifier<C> subClassifier; Rule(String attValue, EntityClassifier<C> subClassifier) { this.attValue = attValue; this.subClassifier = subClassifier; } } /** * Maintains attribute value rules for one attribute name and * optionally a default sub-classifier which is applied if none * of the rules matches. * @author R. Lunde */ private static class RuleGroup<C> { String attName; List<Rule<C>> attValueRules; EntityClassifier<C> defaultSubClassifier; RuleGroup(String attName) { this.attName = attName; attValueRules = new ArrayList<Rule<C>>(); } } }