package com.thaiopensource.relaxng.pattern; import com.thaiopensource.util.VoidValue; import com.thaiopensource.xml.util.Name; import org.relaxng.datatype.Datatype; import org.xml.sax.ErrorHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class IdTypeMapBuilder { private boolean hadError; private final ErrorHandler eh; private final PatternFunction<Integer> idTypeFunction = new IdTypeFunction(); private final IdTypeMapImpl idTypeMap = new IdTypeMapImpl(); private final Set<ElementPattern> elementProcessed = new HashSet<ElementPattern>(); private final List<PossibleConflict> possibleConflicts = new ArrayList<PossibleConflict>(); private void notePossibleConflict(NameClass elementNameClass, NameClass attributeNameClass, Locator loc) { possibleConflicts.add(new PossibleConflict(elementNameClass, attributeNameClass, loc)); } private static class WrappedSAXException extends RuntimeException { private final SAXException cause; WrappedSAXException(SAXException cause) { this.cause = cause; } } private static class PossibleConflict { private final NameClass elementNameClass; private final NameClass attributeNameClass; private final Locator locator; private PossibleConflict(NameClass elementNameClass, NameClass attributeNameClass, Locator locator) { this.elementNameClass = elementNameClass; this.attributeNameClass = attributeNameClass; this.locator = locator; } } private static class ScopedName { private final Name elementName; private final Name attributeName; private ScopedName(Name elementName, Name attributeName) { this.elementName = elementName; this.attributeName = attributeName; } public int hashCode() { return elementName.hashCode() ^ attributeName.hashCode(); } public boolean equals(Object obj) { if (!(obj instanceof ScopedName)) return false; ScopedName other = (ScopedName)obj; return elementName.equals(other.elementName) && attributeName.equals(other.attributeName); } } private static class IdTypeMapImpl implements IdTypeMap { private final Map<ScopedName, Integer> table = new HashMap<ScopedName, Integer>(); public int getIdType(Name elementName, Name attributeName) { Integer n = table.get(new ScopedName(elementName, attributeName)); if (n == null) return Datatype.ID_TYPE_NULL; return n; } private void add(Name elementName, Name attributeName, int idType) { table.put(new ScopedName(elementName, attributeName), idType); } } private class IdTypeFunction extends AbstractPatternFunction<Integer> { public Integer caseOther(Pattern p) { return Datatype.ID_TYPE_NULL; } public Integer caseData(DataPattern p) { return p.getDatatype().getIdType(); } public Integer caseDataExcept(DataExceptPattern p) { return p.getDatatype().getIdType(); } public Integer caseValue(ValuePattern p) { return p.getDatatype().getIdType(); } } private class BuildFunction extends AbstractPatternFunction<VoidValue> { private final NameClass elementNameClass; private final Locator locator; private final boolean attributeIsParent; BuildFunction(NameClass elementNameClass, Locator locator) { this.elementNameClass = elementNameClass; this.locator = locator; this.attributeIsParent = false; } BuildFunction(NameClass elementNameClass, Locator locator, boolean attributeIsParent) { this.elementNameClass = elementNameClass; this.locator = locator; this.attributeIsParent = attributeIsParent; } private BuildFunction down() { if (!attributeIsParent) return this; return new BuildFunction(elementNameClass, locator, false); } public VoidValue caseChoice(ChoicePattern p) { BuildFunction f = down(); p.getOperand1().apply(f); p.getOperand2().apply(f); return VoidValue.VOID; } public VoidValue caseInterleave(InterleavePattern p) { BuildFunction f = down(); p.getOperand1().apply(f); p.getOperand2().apply(f); return VoidValue.VOID; } public VoidValue caseGroup(GroupPattern p) { BuildFunction f = down(); p.getOperand1().apply(f); p.getOperand2().apply(f); return VoidValue.VOID; } public VoidValue caseOneOrMore(OneOrMorePattern p) { p.getOperand().apply(down()); return VoidValue.VOID; } public VoidValue caseElement(ElementPattern p) { if (elementProcessed.contains(p)) return VoidValue.VOID; elementProcessed.add(p); p.getContent().apply(new BuildFunction(p.getNameClass(), p.getLocator())); return VoidValue.VOID; } public VoidValue caseAttribute(AttributePattern p) { int idType = p.getContent().apply(idTypeFunction); if (idType != Datatype.ID_TYPE_NULL) { NameClass attributeNameClass = p.getNameClass(); if (!(attributeNameClass instanceof SimpleNameClass)) { error("id_attribute_name_class", p.getLocator()); return VoidValue.VOID; } elementNameClass.accept(new ElementNameClassVisitor(((SimpleNameClass)attributeNameClass).getName(), locator, idType)); } else notePossibleConflict(elementNameClass, p.getNameClass(), locator); p.getContent().apply(new BuildFunction(null, p.getLocator(), true)); return VoidValue.VOID; } private void datatype(Datatype dt) { if (dt.getIdType() != Datatype.ID_TYPE_NULL && !attributeIsParent) error("id_parent", locator); } public VoidValue caseData(DataPattern p) { datatype(p.getDatatype()); return VoidValue.VOID; } public VoidValue caseDataExcept(DataExceptPattern p) { datatype(p.getDatatype()); p.getExcept().apply(down()); return VoidValue.VOID; } public VoidValue caseValue(ValuePattern p) { datatype(p.getDatatype()); return VoidValue.VOID; } public VoidValue caseList(ListPattern p) { p.getOperand().apply(down()); return VoidValue.VOID; } public VoidValue caseOther(Pattern p) { return VoidValue.VOID; } } private class ElementNameClassVisitor implements NameClassVisitor { private final Name attributeName; private final Locator locator; private final int idType; ElementNameClassVisitor(Name attributeName, Locator locator, int idType) { this.attributeName = attributeName; this.locator = locator; this.idType = idType; } public void visitChoice(NameClass nc1, NameClass nc2) { nc1.accept(this); nc2.accept(this); } public void visitName(Name elementName) { int tem = idTypeMap.getIdType(elementName, attributeName); if (tem != Datatype.ID_TYPE_NULL && tem != idType) error("id_type_conflict", elementName, attributeName, locator); idTypeMap.add(elementName, attributeName, idType); } public void visitNsName(String ns) { visitOther(); } public void visitNsNameExcept(String ns, NameClass nc) { visitOther(); } public void visitAnyName() { visitOther(); } public void visitAnyNameExcept(NameClass nc) { visitOther(); } public void visitNull() { } public void visitError() { } private void visitOther() { error("id_element_name_class", locator); } } private void error(String key, Locator locator) { hadError = true; if (eh != null) try { eh.error(new SAXParseException(SchemaBuilderImpl.localizer.message(key), locator)); } catch (SAXException e) { throw new WrappedSAXException(e); } } private void error(String key, Name arg1, Name arg2, Locator locator) { hadError = true; if (eh != null) try { eh.error(new SAXParseException(SchemaBuilderImpl.localizer.message(key, NameFormatter.format(arg1), NameFormatter.format(arg2)), locator)); } catch (SAXException e) { throw new WrappedSAXException(e); } } public IdTypeMapBuilder(ErrorHandler eh, Pattern pattern) throws SAXException { this.eh = eh; try { pattern.apply(new BuildFunction(null, null)); for (PossibleConflict pc : possibleConflicts) { if (pc.elementNameClass instanceof SimpleNameClass && pc.attributeNameClass instanceof SimpleNameClass) { Name elementName = ((SimpleNameClass)pc.elementNameClass).getName(); Name attributeName = ((SimpleNameClass)pc.attributeNameClass).getName(); int idType = idTypeMap.getIdType(elementName, attributeName); if (idType != Datatype.ID_TYPE_NULL) error("id_type_conflict", elementName, attributeName, pc.locator); } else { for (ScopedName sn : idTypeMap.table.keySet()) { if (pc.elementNameClass.contains(sn.elementName) && pc.attributeNameClass.contains(sn.attributeName)) { error("id_type_conflict", sn.elementName, sn.attributeName, pc.locator); break; } } } } } catch (WrappedSAXException e) { throw e.cause; } } public IdTypeMap getIdTypeMap() { if (hadError) return null; return idTypeMap; } }