/* * #! * Ontopia OSL Schema * #- * Copyright (C) 2001 - 2014 The Ontopia Project * #- * 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 net.ontopia.topicmaps.schema.impl.osl; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Stack; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.schema.core.CardinalityConstraintIF; import net.ontopia.topicmaps.schema.core.SchemaSyntaxException; import net.ontopia.topicmaps.schema.core.TMObjectMatcherIF; import net.ontopia.utils.OntopiaRuntimeException; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.LocatorImpl; /** * INTERNAL: SAX2 content handler used for importing OSL topic map * schemas into the schema object model. */ public class OSLSchemaContentHandler extends DefaultHandler { protected LocatorIF base_address; protected OSLSchema schema; protected String curelem; protected Locator saxlocator; protected Stack openElements; protected Stack openObjects; protected List forwardrefs; public OSLSchemaContentHandler(LocatorIF base_address) { this.base_address = base_address; } // --- Actual builder implementation public void beginElement(String name, Attributes attrs) throws java.net.MalformedURLException, SAXException { curelem = name; if (name == "ruleset") { OSLSchema parent = getSchema(); RuleSet ruleset = new RuleSet(parent, attrs.getValue("id")); parent.addRuleSet(ruleset); openObjects.push(ruleset); } else if (name == "tm-schema") { if (attrs.getValue("match") != null) schema.setIsStrict(getTrueFalse(attrs.getValue("match"), "strict", "loose")); openObjects.push(schema); } else if (name == "baseName") { TopicConstraintCollection parent = getTopicConstraintCollection(); TopicNameConstraint constraint = new TopicNameConstraint(parent); setMinMax(constraint, attrs); parent.addTopicNameConstraint(constraint); openObjects.push(constraint); } else if (name == "variant") { TopicNameConstraint parent = getTopicNameConstraint(); VariantConstraint constraint = new VariantConstraint(parent); setMinMax(constraint, attrs); parent.addVariantConstraint(constraint); openObjects.push(constraint); } else if (name == "occurrence") { TopicConstraintCollection parent = getTopicConstraintCollection(); OccurrenceConstraint constraint = new OccurrenceConstraint(parent); String internal = attrs.getValue("internal"); if (internal == null || internal.equalsIgnoreCase("either")) constraint.setInternal(OccurrenceConstraint.RESOURCE_EITHER); else if (internal.equals("yes")) constraint.setInternal(OccurrenceConstraint.RESOURCE_INTERNAL); else if (internal.equals("no")) constraint.setInternal(OccurrenceConstraint.RESOURCE_EXTERNAL); else throw getException("Attribute 'internal' had illegal value " + internal); setMinMax(constraint, attrs); parent.addOccurrenceConstraint(constraint); openObjects.push(constraint); } else if (name == "playing") { TopicConstraintCollection parent = getTopicConstraintCollection(); TopicRoleConstraint constraint = new TopicRoleConstraint(parent); setMinMax(constraint, attrs); parent.addRoleConstraint(constraint); openObjects.push(constraint); } else if (name == "in") { TopicRoleConstraint parent = getTopicRoleConstraint(); openObjects.push(parent); } else if (name == "scope") { ScopedConstraintIF parent = getScopedConstraint(); ScopeSpecification spec = new ScopeSpecification(); if (attrs.getValue("match") != null) { String match = attrs.getValue("match"); if (match.equalsIgnoreCase("exact")) spec.setMatch(ScopeSpecification.MATCH_EXACT); else if (match.equalsIgnoreCase("subset")) spec.setMatch(ScopeSpecification.MATCH_SUBSET); else if (match.equalsIgnoreCase("superset")) spec.setMatch(ScopeSpecification.MATCH_SUPERSET); else throw getException("Attribute match had illegal value " + match); } parent.setScopeSpecification(spec); openObjects.push(spec); } else if (name == "topic") { OSLSchema parent = getSchema(); TopicClass topicClass = new TopicClass(parent, attrs.getValue("id")); if (attrs.getValue("match") != null) topicClass.setIsStrict(getTrueFalse(attrs.getValue("match"), "strict", "loose")); parent.addTopicClass(topicClass); openObjects.push(topicClass); } else if (name == "association") { OSLSchema parent = getSchema(); AssociationClass assocClass = new AssociationClass(parent); parent.addAssociationClass(assocClass); openObjects.push(assocClass); } else if (name == "role") { AssociationClass parent = getAssociationClass(); AssociationRoleConstraint constraint = new AssociationRoleConstraint(parent); setMinMax(constraint, attrs); parent.addRoleConstraint(constraint); openObjects.push(constraint); } else if (name == "player") { AssociationRoleConstraint parent = getAssociationRoleConstraint(); TypeSpecification spec = new TypeSpecification(); if (attrs.getValue("subclasses") != null) spec.setSubclasses(getTrueFalse(attrs.getValue("subclasses"), "yes", "no")); parent.addPlayerType(spec); openObjects.push(spec); } else if (name == "otherClass") { TopicClass parent = getTopicClass(); TypeSpecification spec = new TypeSpecification(); if (attrs.getValue("subclasses") != null) spec.setSubclasses(getTrueFalse(attrs.getValue("subclasses"), "yes", "no")); parent.addOtherClass(spec); openObjects.push(spec); } else if (name == "instanceOf") { TypeSpecification spec = new TypeSpecification(); if (attrs.getValue("subclasses") != null) spec.setSubclasses(getTrueFalse(attrs.getValue("subclasses"), "yes", "no")); if (openElements.peek() == "scope") getScopeSpecification().addThemeMatcher(spec); else if (openElements.peek() == "in") getTopicRoleConstraint().addAssociationType(spec); else getTypedConstraint().setTypeSpecification(spec); openObjects.push(spec); } else if (name == "topicRef" || name == "subjectIndicatorRef" || name == "internalTopicRef") { String href = attrs.getValue("href"); if (href == null) throw getException("The href attribute on " + name + " is required"); TMObjectMatcherIF matcher; if (name == "topicRef") matcher = new SourceLocatorMatcher(base_address.resolveAbsolute(href)); else if (name == "subjectIndicatorRef") matcher = new SubjectIndicatorMatcher(base_address.resolveAbsolute(href)); else if (name == "internalTopicRef") matcher = new InternalTopicRefMatcher(href); else throw new OntopiaRuntimeException("INTERNAL ERROR!"); String parent = (String) openElements.peek(); if (parent == "scope") getScopeSpecification().addThemeMatcher(matcher); else if (parent == "instanceOf" || parent == "player" || parent == "otherClass") getTypeSpecification().setClassMatcher(matcher); else throw getException(name + " must have scope, instanceOf, otherClass, or player as parent"); openObjects.push(null); } else if (name == "any") { TMObjectMatcherIF matcher = new AnyTopicMatcher(); String parent = (String) openElements.peek(); if (parent == "scope") getScopeSpecification().addThemeMatcher(matcher); else if (parent == "instanceOf" || parent == "player") getTypeSpecification().setClassMatcher(matcher); else throw getException("topicRef must have scope, instanceOf, or player as parent"); openObjects.push(null); } else if (name == "ruleref") { String ruleid = attrs.getValue("rule"); if (ruleid == null) throw getException("rule attribute on ruleref must have a value"); RuleSet rule = schema.getRuleSet(ruleid); if (rule == null) throw getException("Reference to non-existent rule " + ruleid); getTopicConstraintCollection().addSubRule(rule); openObjects.push(rule); } else if (name == "superclass") { String refid = attrs.getValue("ref"); if (refid == null) throw getException("ref attribute on superclass must have a value"); TopicClass superclass = schema.getTopicClass(refid); if (superclass == null) { Locator location = null; if (saxlocator != null) location = new LocatorImpl(saxlocator); forwardrefs.add(new ForwardReference(getTopicClass(), refid, location)); // it's OK to let the null pass on } getTopicClass().setSuperclass(superclass); openObjects.push(superclass); } else throw getException("Unknown element " + name); openElements.push(name); } public void stopElement(String name) throws SAXException { if ((name == "topic" || name == "role" || name == "playing" || name == "association" || name == "occurrence") && getTypedConstraint().getTypeSpecification() == null) throw getException("<" + name + "> element with no type specification"); else if ((name == "variant" || name == "baseName") && getScopedConstraint().getScopeSpecification() == null) throw getException("<" + name + "> element with no scope specification"); if (name == "variant") { VariantConstraint variant = getVariantConstraint(); inheritScope(variant.getParent(), variant); } openElements.pop(); openObjects.pop(); } // --- ContentHandler implementation public void startDocument() { schema = new OSLSchema(base_address); openElements = new Stack(); openObjects = new Stack(); forwardrefs = new ArrayList(); } public void startElement(String name, Attributes attrs) throws SAXException { try { beginElement(name, attrs); } // catch (ClassCastException e) { // FIXME: remove! // e.printStackTrace(); // throw e; // } // catch (NullPointerException e) { // FIXME: remove! // e.printStackTrace(); // throw e; // } catch (java.net.MalformedURLException e) { throw new OntopiaRuntimeException(e); } } public void endElement(String name) throws SAXException { stopElement(name); } public void endDocument() throws SAXException { Iterator it = forwardrefs.iterator(); while (it.hasNext()) { ForwardReference ref = (ForwardReference) it.next(); TopicClass superclass = schema.getTopicClass(ref.refid); if (superclass == null) { String message = "Superclass reference to undefined class"; throw new SAXException(new SchemaSyntaxException(message, ref.location)); } ref.subclass.setSuperclass(superclass); } } // --- For namespace-insistent parsers public void startElement(String uri, String lname, String qname, Attributes attrs) throws SAXException { startElement(qname, attrs); } public void endElement(String uri, String lname, String qname) throws SAXException { endElement(qname); } // --- Retrieving current objects public OSLSchema getSchema() { return schema; } protected TopicConstraintCollection getTopicConstraintCollection() throws SAXException { verifyParent("ruleset", "topic"); return (TopicConstraintCollection) openObjects.peek(); } protected TopicClass getTopicClass() throws SAXException { verifyParent("topic"); return (TopicClass) openObjects.peek(); } protected AssociationClass getAssociationClass() throws SAXException { verifyParent("association"); return (AssociationClass) openObjects.peek(); } protected TopicRoleConstraint getTopicRoleConstraint() throws SAXException { verifyParent("playing", "in"); return (TopicRoleConstraint) openObjects.peek(); } protected AssociationRoleConstraint getAssociationRoleConstraint() throws SAXException { verifyParent("role"); return (AssociationRoleConstraint) openObjects.peek(); } protected TopicNameConstraint getTopicNameConstraint() throws SAXException { verifyParent("baseName"); return (TopicNameConstraint) openObjects.peek(); } protected VariantConstraint getVariantConstraint() throws SAXException { verifyParent("variant"); return (VariantConstraint) openObjects.peek(); } protected ScopedConstraintIF getScopedConstraint() throws SAXException { String[] parents ={"baseName", "variant", "occurrence", "association"}; verifyParent(parents); return (ScopedConstraintIF) openObjects.peek(); } protected TypedConstraintIF getTypedConstraint() throws SAXException { String[] parents ={"topic", "playing", "in", "occurrence", "association", "role"}; verifyParent(parents); return (TypedConstraintIF) openObjects.peek(); } protected ScopeSpecification getScopeSpecification() throws SAXException { verifyParent("scope"); return (ScopeSpecification) openObjects.peek(); } protected TypeSpecification getTypeSpecification() throws SAXException { String[] parents = {"instanceOf", "player", "otherClass"}; verifyParent(parents); return (TypeSpecification) openObjects.peek(); } // --- SAX helpers public void setDocumentLocator(Locator locator) { saxlocator = locator; } protected void verifyParent(String name) throws SAXException { if (!name.equals(openElements.peek())) throw getException("Expected parent of element " + curelem + " to be " + name + ", but it was " + openElements.peek()); } protected void verifyParent(String name1, String name2) throws SAXException { if (!name1.equals(openElements.peek()) && !name2.equals(openElements.peek())) throw getException("Expected parent of element " + curelem +" to be " + name1 + " or " + name2 + ", but it was " + openElements.peek()); } protected void verifyParent(String[] names) throws SAXException { String parent = (String) openElements.peek(); for (int ix = 0; ix < names.length; ix++) if (names[ix].equals(parent)) return; throw getException("Element " + curelem + " had illegal parent " + parent); } protected SAXException getException(String message) { Locator location = null; if (saxlocator != null) location = new LocatorImpl(saxlocator); return new SAXException(new SchemaSyntaxException(message, location)); } // --- Population helpers protected void inheritScope(ScopedConstraintIF parent, ScopedConstraintIF child) { ScopeSpecification spec = parent.getScopeSpecification(); ScopeSpecification childspec = child.getScopeSpecification(); Iterator it = spec.getThemeMatchers().iterator(); while (it.hasNext()) { TMObjectMatcherIF matcher = (TMObjectMatcherIF) it.next(); childspec.addThemeMatcher(matcher); } } protected void setMinMax(CardinalityConstraintIF constraint, Attributes attrs) throws SAXException { String curattr = "min"; // used for exception error message try { int min = 0; if (attrs.getValue("min") != null) min = Integer.parseInt(attrs.getValue("min")); int max = CardinalityConstraintIF.INFINITY; curattr = "max"; if (attrs.getValue("max") != null) { if (!attrs.getValue("max").equalsIgnoreCase("inf")) { max = Integer.parseInt(attrs.getValue("max")); if (max == -1) throw getException("Cannot set max to negative value"); } } if (max != -1 && min > max) throw getException("min(" + min + ") > max(" + max + ")"); constraint.setMinimum(min); constraint.setMaximum(max); } catch (NumberFormatException e) { throw getException("Attribute " + curattr + " contained '" + attrs.getValue(curattr) + "', not an integer"); } } protected boolean getTrueFalse(String value, String tvalue, String fvalue) throws SAXException { if (value.equalsIgnoreCase(tvalue)) return true; else if (value.equalsIgnoreCase(fvalue)) return false; else throw getException("Expected " + tvalue + " or " + fvalue + " as attribute values, found " + value); } // --- Internal data holder class class ForwardReference { public TopicClass subclass; public String refid; public Locator location; public ForwardReference(TopicClass subclass, String refid, Locator location) { this.subclass = subclass; this.refid = refid; this.location = location; } // this is just a data holder, so we don't bother with get methods } }