001    package com.thaiopensource.validate.mns;
002    
003    import com.thaiopensource.util.Localizer;
004    import com.thaiopensource.util.PropertyMap;
005    import com.thaiopensource.util.Uri;
006    import com.thaiopensource.util.PropertyMapBuilder;
007    import com.thaiopensource.validate.IncorrectSchemaException;
008    import com.thaiopensource.validate.Schema;
009    import com.thaiopensource.validate.ValidateProperty;
010    import com.thaiopensource.validate.Validator;
011    import com.thaiopensource.validate.AbstractSchema;
012    import com.thaiopensource.validate.auto.SchemaFuture;
013    import com.thaiopensource.xml.sax.XmlBaseHandler;
014    import com.thaiopensource.xml.sax.DelegatingContentHandler;
015    import com.thaiopensource.xml.sax.CountingErrorHandler;
016    import com.thaiopensource.xml.util.Name;
017    import com.thaiopensource.xml.util.StringSplitter;
018    import com.thaiopensource.xml.util.WellKnownNamespaces;
019    import org.xml.sax.Attributes;
020    import org.xml.sax.ErrorHandler;
021    import org.xml.sax.InputSource;
022    import org.xml.sax.Locator;
023    import org.xml.sax.SAXException;
024    import org.xml.sax.SAXParseException;
025    import org.xml.sax.XMLReader;
026    import org.xml.sax.helpers.LocatorImpl;
027    
028    import java.io.IOException;
029    import java.util.Enumeration;
030    import java.util.Hashtable;
031    import java.util.Stack;
032    
033    class SchemaImpl extends AbstractSchema {
034      static final String MNS_URI = "http://www.thaiopensource.com/ns/mns";
035      private final Hashtable modeMap = new Hashtable();
036      private Mode startMode;
037      private static final String DEFAULT_MODE_NAME = "#default";
038      private final boolean attributesSchema;
039    
040      static private final class WrappedIOException extends RuntimeException {
041        private final IOException exception;
042    
043        private WrappedIOException(IOException exception) {
044          this.exception = exception;
045        }
046    
047        private IOException getException() {
048          return exception;
049        }
050      }
051    
052      static class ElementAction {
053        private final Schema schema;
054        private final Mode mode;
055        private final ContextMap contextMap;
056        private final ElementsOrAttributes prune;
057        private final Hashset covered = new Hashset();
058    
059        ElementAction(String ns, Schema schema, Mode mode, ContextMap contextMap, ElementsOrAttributes prune) {
060          this.schema = schema;
061          this.mode = mode;
062          this.contextMap = contextMap;
063          this.prune = prune;
064          covered.add(ns);
065        }
066    
067        Mode getMode() {
068          return mode;
069        }
070    
071        ContextMap getContextMap() {
072          return contextMap;
073        }
074    
075        Schema getSchema() {
076          return schema;
077        }
078    
079        ElementsOrAttributes getPrune() {
080          return prune;
081        }
082    
083        Hashset getCoveredNamespaces() {
084          return covered;
085        }
086      }
087    
088      static class Mode {
089        private Locator whereDefined;
090        private boolean defined = false;
091        private ElementsOrAttributes lax;
092        private boolean strictDefined = false;
093        private final Hashtable elementMap = new Hashtable();
094        private final Hashtable attributesMap = new Hashtable();
095    
096        Mode(ElementsOrAttributes lax) {
097          this.lax = lax;
098        }
099    
100        ElementsOrAttributes getLax() {
101          return lax;
102        }
103    
104        Schema getAttributesSchema(String ns) {
105          return (Schema)attributesMap.get(ns);
106        }
107    
108        ElementAction getElementAction(String ns) {
109          return (ElementAction)elementMap.get(ns);
110        }
111      }
112    
113      private class Handler extends DelegatingContentHandler implements SchemaFuture {
114        private final SchemaReceiverImpl sr;
115        private ElementAction currentElementAction;
116        private boolean hadError = false;
117        private final ErrorHandler eh;
118        private final CountingErrorHandler ceh;
119        private final Localizer localizer = new Localizer(SchemaImpl.class);
120        private Locator locator;
121        private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler();
122        private int foreignDepth = 0;
123        private String contextNs;
124        private Mode contextMode;
125        private String elementNs;
126        private String defaultSchemaType;
127        private final Stack nameStack = new Stack();
128        private boolean isRoot;
129        private int pathDepth = 0;
130        private Validator validator;
131    
132    
133        Handler(SchemaReceiverImpl sr) {
134          this.sr = sr;
135          this.eh = ValidateProperty.ERROR_HANDLER.get(sr.getProperties());
136          this.ceh = new CountingErrorHandler(eh);
137        }
138    
139        public void setDocumentLocator(Locator locator) {
140          xmlBaseHandler.setLocator(locator);
141          this.locator = locator;
142        }
143    
144        public void startDocument() throws SAXException {
145          try {
146            PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties());
147            ValidateProperty.ERROR_HANDLER.put(builder, ceh);
148            validator = sr.getMnsSchema().createValidator(builder.toPropertyMap());
149          }
150          catch (IOException e) {
151            throw new WrappedIOException(e);
152          }
153          catch (IncorrectSchemaException e) {
154            throw new RuntimeException("internal error in RNG schema for MNS");
155          }
156          setDelegate(validator.getContentHandler());
157          if (locator != null)
158            super.setDocumentLocator(locator);
159          super.startDocument();
160        }
161    
162        public Schema getSchema() throws IncorrectSchemaException, SAXException {
163          if (validator == null || ceh.getHadErrorOrFatalError())
164            throw new IncorrectSchemaException();
165          for (Enumeration en = modeMap.keys(); en.hasMoreElements();) {
166            String modeName = (String)en.nextElement();
167            Mode mode = (Mode)modeMap.get(modeName);
168            if (!mode.defined && !modeName.equals(DEFAULT_MODE_NAME))
169              error("undefined_mode", modeName, mode.whereDefined);
170          }
171          if (hadError)
172            throw new IncorrectSchemaException();
173          return SchemaImpl.this;
174        }
175    
176        public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException {
177          if (e instanceof WrappedIOException)
178            throw ((WrappedIOException)e).getException();
179          return e;
180        }
181    
182        public void startElement(String uri, String localName,
183                                 String qName, Attributes attributes)
184                throws SAXException {
185          super.startElement(uri, localName, qName, attributes);
186          xmlBaseHandler.startElement();
187          String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base");
188          if (xmlBase != null)
189            xmlBaseHandler.xmlBaseAttribute(xmlBase);
190          if (!MNS_URI.equals(uri) || foreignDepth > 0) {
191            foreignDepth++;
192            return;
193          }
194          if (ceh.getHadErrorOrFatalError())
195            return;
196          if (localName.equals("rules"))
197            parseRules(attributes);
198          else if (localName.equals("cover"))
199            parseCover(attributes);
200          else if (localName.equals("context"))
201            parseContext(attributes);
202          else if (localName.equals("root"))
203            parseRoot(attributes);
204          else if (localName.equals("element"))
205            parseElement(attributes);
206          else if (localName.equals("lax"))
207            parseLax(attributes);
208          else
209            parseValidate(localName.equals("validateAttributes"), attributes);
210        }
211    
212        public void endElement(String namespaceURI, String localName,
213                               String qName)
214                throws SAXException {
215          super.endElement(namespaceURI, localName, qName);
216          xmlBaseHandler.endElement();
217          if (foreignDepth > 0) {
218            foreignDepth--;
219            return;
220          }
221         if (pathDepth > 0) {
222            pathDepth--;
223            if (pathDepth == 0)
224              endPath();
225          }
226        }
227    
228    
229        private void parseRules(Attributes attributes) {
230          String modeName = attributes.getValue("", "startMode");
231          if (modeName == null)
232            modeName = DEFAULT_MODE_NAME;
233          defaultSchemaType = getSchemaType(attributes);
234          startMode = lookupCreateMode(modeName);
235        }
236    
237        private void parseCover(Attributes attributes) throws SAXException {
238          String ns = getNs(attributes, false);
239          currentElementAction.covered.add(ns);
240        }
241    
242        private void parseLax(Attributes attributes) throws SAXException {
243          String[] modeNames = getInModes(attributes);
244          Mode[] modes = getModes(modeNames);
245          ElementsOrAttributes lax = toElementsOrAttributes(attributes.getValue("", "allow"),
246                                                            ElementsOrAttributes.BOTH);
247          for (int i = 0; i < modes.length; i++) {
248            if (modes[i].strictDefined)
249              error("lax_multiply_defined", modeNames[i]);
250            else {
251              modes[i].lax = lax;
252              modes[i].strictDefined = true;
253            }
254          }
255        }
256    
257        private void parseValidate(boolean isAttribute, Attributes attributes) throws SAXException {
258          String[] modeNames = getInModes(attributes);
259          Mode[] modes = getModes(modeNames);
260          String ns = getNs(attributes, isAttribute);
261          String schemaUri = getSchema(attributes);
262          String schemaType = getSchemaType(attributes);
263          if (schemaType == null)
264            schemaType = defaultSchemaType;
265          try {
266            if (isAttribute) {
267              Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, true);
268              for (int i = 0; i < modes.length; i++) {
269                if (modes[i].attributesMap.get(ns) != null)
270                  error("validate_attributes_multiply_defined", modeNames[i], ns);
271                else
272                  modes[i].attributesMap.put(ns, schema);
273              }
274            }
275            else {
276              Schema schema = sr.createChildSchema(new InputSource(schemaUri), schemaType, false);
277              currentElementAction = new ElementAction(ns,
278                                                       schema,
279                                                       getUseMode(attributes),
280                                                       new ContextMap(),
281                                                       getPrune(attributes));
282              contextNs = ns;
283              for (int i = 0; i < modes.length; i++) {
284                if (modes[i].elementMap.get(ns) != null)
285                  error("validate_element_multiply_defined", modeNames[i], ns);
286                else
287                  modes[i].elementMap.put(ns, currentElementAction);
288              }
289            }
290          }
291          catch (IncorrectSchemaException e) {
292            hadError = true;
293          }
294          catch (IOException e) {
295            throw new WrappedIOException(e);
296          }
297        }
298    
299        private void parseContext(Attributes attributes) throws SAXException {
300          String ns = getNs(attributes, false);
301          if (ns != null)
302            contextNs = ns;
303          elementNs = contextNs;
304          contextMode = getUseMode(attributes);
305        }
306    
307        private void parseRoot(Attributes attributes) throws SAXException {
308          String ns = getNs(attributes, false);
309          if (ns != null)
310            elementNs = ns;
311          isRoot = true;
312          pathDepth++;
313        }
314    
315        private void parseElement(Attributes attributes) throws SAXException {
316          String ns = getNs(attributes, false);
317          if (ns != null)
318            elementNs = ns;
319          if (!currentElementAction.covered.contains(elementNs))
320            error("context_ns_not_covered", elementNs);
321          nameStack.push(new Name(elementNs, attributes.getValue("", "name").trim()));
322          pathDepth++;
323        }
324    
325        private void endPath() throws SAXException {
326          if (!currentElementAction.contextMap.put(isRoot, nameStack, contextMode))
327            error("path_multiply_defined", displayPath(isRoot, nameStack));
328          elementNs = contextNs;
329          isRoot = false;
330          nameStack.setSize(0);
331        }
332    
333        private String displayPath(boolean isRoot, Stack nameStack) {
334          StringBuffer buf = new StringBuffer();
335          for (int i = 0, len = nameStack.size(); i < len; i++) {
336            if (i > 0 || isRoot)
337              buf.append('/');
338            Name name = (Name)nameStack.elementAt(i);
339            if (name.getNamespaceUri().length() > 0) {
340              buf.append('{');
341              buf.append(name.getNamespaceUri());
342              buf.append('}');
343            }
344            buf.append(name.getLocalName());
345          }
346          return buf.toString();
347        }
348    
349        private String getSchema(Attributes attributes) throws SAXException {
350          String schemaUri = attributes.getValue("", "schema");
351          if (Uri.hasFragmentId(schemaUri))
352            error("schema_fragment_id");
353          return Uri.resolve(xmlBaseHandler.getBaseUri(),
354                             Uri.escapeDisallowedChars(schemaUri));
355        }
356    
357        private String getSchemaType(Attributes attributes) {
358          return attributes.getValue("", "schemaType");
359        }
360    
361        private ElementsOrAttributes getPrune(Attributes attributes) {
362          return toElementsOrAttributes(attributes.getValue("", "prune"),
363                                        ElementsOrAttributes.NEITHER);
364        }
365    
366        private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) {
367          if (value == null)
368            return defaultValue;
369          ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER;
370          if (value.indexOf("elements") >= 0)
371            eoa = eoa.addElements();
372          if (value.indexOf("attributes") >= 0)
373            eoa = eoa.addAttributes();
374          return eoa;
375        }
376    
377        private Mode getUseMode(Attributes attributes) {
378          String modeName = attributes.getValue("", "useMode");
379          if (modeName == null)
380            modeName = DEFAULT_MODE_NAME;
381          Mode mode = lookupCreateMode(modeName);
382          if (mode.whereDefined == null && locator != null)
383            mode.whereDefined = new LocatorImpl(locator);
384          return mode;
385        }
386    
387        private String getNs(Attributes attributes, boolean forbidEmpty) throws SAXException {
388          String ns = attributes.getValue("", "ns");
389          if (ns != null && !Uri.isAbsolute(ns) && (forbidEmpty || !ns.equals("")))
390            error("ns_absolute");
391          return ns;
392        }
393    
394        private Mode[] getModes(String[] modeNames) {
395          Mode[] modes = new Mode[modeNames.length];
396          for (int i = 0; i < modes.length; i++) {
397            modes[i] = lookupCreateMode(modeNames[i]);
398            modes[i].defined = true;
399          }
400          return modes;
401        }
402    
403        private String[] getInModes(Attributes attributes) {
404          String inModes = attributes.getValue("", "inModes");
405          if (inModes == null)
406            return new String[] { DEFAULT_MODE_NAME };
407          return StringSplitter.split(inModes);
408        }
409    
410    
411        void error(String key) throws SAXException {
412          hadError = true;
413          if (eh == null)
414            return;
415          eh.error(new SAXParseException(localizer.message(key), locator));
416        }
417    
418        void error(String key, String arg) throws SAXException {
419          hadError = true;
420          if (eh == null)
421            return;
422          eh.error(new SAXParseException(localizer.message(key, arg), locator));
423        }
424    
425        void error(String key, String arg, Locator locator) throws SAXException {
426          hadError = true;
427          if (eh == null)
428            return;
429          eh.error(new SAXParseException(localizer.message(key, arg), locator));
430        }
431    
432        void error(String key, String arg1, String arg2) throws SAXException {
433          hadError = true;
434          if (eh == null)
435            return;
436          eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator));
437        }
438    
439      }
440    
441      SchemaImpl(boolean attributesSchema) {
442        this.attributesSchema = attributesSchema;
443      }
444    
445      SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) {
446        Handler h = new Handler(sr);
447        in.setContentHandler(h);
448        return h;
449      }
450    
451      public Validator createValidator(PropertyMap properties) {
452        return new ValidatorImpl(startMode, properties);
453      }
454    
455      private Mode lookupCreateMode(String name) {
456        Mode mode = (Mode)modeMap.get(name);
457        if (mode == null) {
458          mode = new Mode(attributesSchema ? ElementsOrAttributes.ELEMENTS : ElementsOrAttributes.NEITHER);
459          modeMap.put(name, mode);
460        }
461        return mode;
462      }
463    
464    }