001    package com.thaiopensource.relaxng.impl;
002    
003    import org.relaxng.datatype.Datatype;
004    import org.xml.sax.ErrorHandler;
005    import org.xml.sax.Locator;
006    import org.xml.sax.SAXException;
007    import org.xml.sax.SAXParseException;
008    
009    import java.util.Enumeration;
010    import java.util.Hashtable;
011    import java.util.Vector;
012    
013    import com.thaiopensource.xml.util.Name;
014    
015    public class IdTypeMapBuilder {
016      private boolean hadError;
017      private final ErrorHandler eh;
018      private final PatternFunction idTypeFunction = new IdTypeFunction();
019      private final IdTypeMapImpl idTypeMap = new IdTypeMapImpl();
020      private final Hashtable elementProcessed = new Hashtable();
021      private final Vector possibleConflicts = new Vector();
022    
023      private void notePossibleConflict(NameClass elementNameClass, NameClass attributeNameClass, Locator loc) {
024        possibleConflicts.addElement(new PossibleConflict(elementNameClass, attributeNameClass, loc));
025      }
026    
027      private static class WrappedSAXException extends RuntimeException {
028        private final SAXException cause;
029        WrappedSAXException(SAXException cause) {
030          this.cause = cause;
031        }
032      }
033    
034      private static class PossibleConflict {
035        private final NameClass elementNameClass;
036        private final NameClass attributeNameClass;
037        private final Locator locator;
038    
039        private PossibleConflict(NameClass elementNameClass, NameClass attributeNameClass, Locator locator) {
040          this.elementNameClass = elementNameClass;
041          this.attributeNameClass = attributeNameClass;
042          this.locator = locator;
043        }
044      }
045    
046      private static class ScopedName {
047        private final Name elementName;
048        private final Name attributeName;
049    
050        private ScopedName(Name elementName, Name attributeName) {
051          this.elementName = elementName;
052          this.attributeName = attributeName;
053        }
054    
055        public int hashCode() {
056          return elementName.hashCode() ^ attributeName.hashCode();
057        }
058    
059        public boolean equals(Object obj) {
060          if (!(obj instanceof ScopedName))
061            return false;
062          ScopedName other = (ScopedName)obj;
063          return elementName.equals(other.elementName) && attributeName.equals(other.attributeName);
064        }
065      }
066    
067      private static class IdTypeMapImpl implements IdTypeMap {
068        private final Hashtable table = new Hashtable();
069        public int getIdType(Name elementName, Name attributeName) {
070          Integer n = (Integer)table.get(new ScopedName(elementName, attributeName));
071          if (n == null)
072            return Datatype.ID_TYPE_NULL;
073          return n.intValue();
074        }
075        private void add(Name elementName, Name attributeName, int idType) {
076          table.put(new ScopedName(elementName, attributeName), new Integer(idType));
077        }
078      }
079    
080      private class IdTypeFunction extends AbstractPatternFunction {
081        public Object caseOther(Pattern p) {
082          return new Integer(Datatype.ID_TYPE_NULL);
083        }
084    
085        public Object caseData(DataPattern p) {
086          return new Integer(p.getDatatype().getIdType());
087        }
088    
089        public Object caseDataExcept(DataExceptPattern p) {
090          return new Integer(p.getDatatype().getIdType());
091        }
092    
093        public Object caseValue(ValuePattern p) {
094          return new Integer(p.getDatatype().getIdType());
095        }
096      }
097    
098      private class BuildFunction extends AbstractPatternFunction {
099        private final NameClass elementNameClass;
100        private final Locator locator;
101        private final boolean attributeIsParent;
102    
103        BuildFunction(NameClass elementNameClass, Locator locator) {
104          this.elementNameClass = elementNameClass;
105          this.locator = locator;
106          this.attributeIsParent = false;
107        }
108    
109       BuildFunction(NameClass elementNameClass, Locator locator, boolean attributeIsParent) {
110          this.elementNameClass = elementNameClass;
111          this.locator = locator;
112          this.attributeIsParent = attributeIsParent;
113        }
114    
115        private BuildFunction down() {
116          if (!attributeIsParent)
117            return this;
118          return new BuildFunction(elementNameClass, locator, false);
119        }
120    
121        public Object caseChoice(ChoicePattern p) {
122          BuildFunction f = down();
123          p.getOperand1().apply(f);
124          p.getOperand2().apply(f);
125          return null;
126        }
127    
128        public Object caseInterleave(InterleavePattern p) {
129          BuildFunction f = down();
130          p.getOperand1().apply(f);
131          p.getOperand2().apply(f);
132          return null;
133        }
134    
135        public Object caseGroup(GroupPattern p) {
136          BuildFunction f = down();
137          p.getOperand1().apply(f);
138          p.getOperand2().apply(f);
139          return null;
140        }
141    
142        public Object caseOneOrMore(OneOrMorePattern p) {
143          p.getOperand().apply(down());
144          return null;
145        }
146    
147        public Object caseElement(ElementPattern p) {
148          if (elementProcessed.get(p) != null)
149            return null;
150          elementProcessed.put(p, p);
151          p.getContent().apply(new BuildFunction(p.getNameClass(), p.getLocator()));
152          return null;
153        }
154    
155        public Object caseAttribute(AttributePattern p) {
156          int idType = ((Integer)p.getContent().apply(idTypeFunction)).intValue();
157          if (idType != Datatype.ID_TYPE_NULL) {
158            NameClass attributeNameClass = p.getNameClass();
159            if (!(attributeNameClass instanceof SimpleNameClass)) {
160              error("id_attribute_name_class", p.getLocator());
161              return null;
162            }
163            elementNameClass.accept(new ElementNameClassVisitor(((SimpleNameClass)attributeNameClass).getName(),
164                                                                locator,
165                                                                idType));
166          }
167          else
168            notePossibleConflict(elementNameClass, p.getNameClass(), locator);
169          p.getContent().apply(new BuildFunction(null, p.getLocator(), true));
170          return null;
171        }
172    
173        private void datatype(Datatype dt) {
174          if (dt.getIdType() != Datatype.ID_TYPE_NULL && !attributeIsParent)
175            error("id_parent", locator);
176        }
177    
178        public Object caseData(DataPattern p) {
179          datatype(p.getDatatype());
180          return null;
181        }
182    
183        public Object caseDataExcept(DataExceptPattern p) {
184          datatype(p.getDatatype());
185          p.getExcept().apply(down());
186          return null;
187        }
188    
189        public Object caseValue(ValuePattern p) {
190          datatype(p.getDatatype());
191          return null;
192        }
193    
194        public Object caseList(ListPattern p) {
195          p.getOperand().apply(down());
196          return null;
197        }
198    
199        public Object caseOther(Pattern p) {
200          return null;
201        }
202      }
203    
204      private class ElementNameClassVisitor implements NameClassVisitor {
205        private final Name attributeName;
206        private final Locator locator;
207        private final int idType;
208    
209        ElementNameClassVisitor(Name attributeName, Locator locator, int idType) {
210          this.attributeName = attributeName;
211          this.locator = locator;
212          this.idType = idType;
213        }
214    
215        public void visitChoice(NameClass nc1, NameClass nc2) {
216          nc1.accept(this);
217          nc2.accept(this);
218        }
219    
220        public void visitName(Name elementName) {
221          int tem = idTypeMap.getIdType(elementName, attributeName);
222          if (tem !=  Datatype.ID_TYPE_NULL && tem != idType)
223            error("id_type_conflict", elementName, attributeName, locator);
224          idTypeMap.add(elementName, attributeName, idType);
225        }
226    
227        public void visitNsName(String ns) {
228          visitOther();
229        }
230    
231        public void visitNsNameExcept(String ns, NameClass nc) {
232          visitOther();
233        }
234    
235        public void visitAnyName() {
236          visitOther();
237        }
238    
239        public void visitAnyNameExcept(NameClass nc) {
240          visitOther();
241        }
242    
243        public void visitNull() {
244        }
245    
246        public void visitError() {
247        }
248    
249        private void visitOther() {
250          error("id_element_name_class", locator);
251        }
252      }
253    
254      private void error(String key, Locator locator) {
255        hadError = true;
256        if (eh != null)
257          try {
258            eh.error(new SAXParseException(SchemaBuilderImpl.localizer.message(key), locator));
259          }
260          catch (SAXException e) {
261            throw new WrappedSAXException(e);
262          }
263      }
264    
265      private void error(String key, Name arg1, Name arg2, Locator locator) {
266       hadError = true;
267       if (eh != null)
268         try {
269           eh.error(new SAXParseException(SchemaBuilderImpl.localizer.message(key, NameFormatter.format(arg1), NameFormatter.format(arg2)),
270                                          locator));
271         }
272         catch (SAXException e) {
273           throw new WrappedSAXException(e);
274         }
275      }
276    
277      public IdTypeMapBuilder(ErrorHandler eh, Pattern pattern) throws SAXException {
278        this.eh = eh;
279        try {
280          pattern.apply(new BuildFunction(null, null));
281          for (Enumeration e = possibleConflicts.elements();
282               e.hasMoreElements();) {
283            PossibleConflict pc = (PossibleConflict)e.nextElement();
284            if (pc.elementNameClass instanceof SimpleNameClass
285                && pc.attributeNameClass instanceof SimpleNameClass) {
286              Name elementName = ((SimpleNameClass)pc.elementNameClass).getName();
287              Name attributeName = ((SimpleNameClass)pc.attributeNameClass).getName();
288              int idType = idTypeMap.getIdType(elementName,
289                                               attributeName);
290              if (idType != Datatype.ID_TYPE_NULL)
291                error("id_type_conflict", elementName, attributeName, pc.locator);
292            }
293            else {
294              for (Enumeration f = idTypeMap.table.keys(); f.hasMoreElements();) {
295                ScopedName sn = (ScopedName)f.nextElement();
296                if (pc.elementNameClass.contains(sn.elementName)
297                    && pc.attributeNameClass.contains(sn.attributeName)) {
298                  error("id_type_conflict", sn.elementName, sn.attributeName, pc.locator);
299                  break;
300                }
301              }
302            }
303          }
304        }
305        catch (WrappedSAXException e) {
306          throw e.cause;
307        }
308      }
309    
310      public IdTypeMap getIdTypeMap() {
311        if (hadError)
312          return null;
313        return idTypeMap;
314      }
315    }