001    package com.thaiopensource.validate.nrl;
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.util.PropertyId;
008    import com.thaiopensource.validate.IncorrectSchemaException;
009    import com.thaiopensource.validate.Schema;
010    import com.thaiopensource.validate.ValidateProperty;
011    import com.thaiopensource.validate.Validator;
012    import com.thaiopensource.validate.Option;
013    import com.thaiopensource.validate.OptionArgumentException;
014    import com.thaiopensource.validate.OptionArgumentPresenceException;
015    import com.thaiopensource.validate.AbstractSchema;
016    import com.thaiopensource.validate.SchemaReader;
017    import com.thaiopensource.validate.auto.SchemaFuture;
018    import com.thaiopensource.xml.sax.XmlBaseHandler;
019    import com.thaiopensource.xml.sax.DelegatingContentHandler;
020    import com.thaiopensource.xml.sax.CountingErrorHandler;
021    import com.thaiopensource.xml.util.WellKnownNamespaces;
022    import org.xml.sax.Attributes;
023    import org.xml.sax.ErrorHandler;
024    import org.xml.sax.InputSource;
025    import org.xml.sax.Locator;
026    import org.xml.sax.SAXException;
027    import org.xml.sax.SAXParseException;
028    import org.xml.sax.XMLReader;
029    import org.xml.sax.helpers.LocatorImpl;
030    
031    import java.io.IOException;
032    import java.util.Enumeration;
033    import java.util.Hashtable;
034    import java.util.Vector;
035    
036    class SchemaImpl extends AbstractSchema {
037      static private final String IMPLICIT_MODE_NAME = "#implicit";
038      static private final String WRAPPER_MODE_NAME = "#wrapper";
039      static final String NRL_URI = SchemaReader.BASE_URI + "nrl";
040      private final Hashtable modeMap = new Hashtable();
041      private Mode startMode;
042      private final Mode defaultBaseMode;
043      private final boolean attributesSchema;
044    
045      static private final class WrappedIOException extends RuntimeException {
046        private final IOException exception;
047    
048        private WrappedIOException(IOException exception) {
049          this.exception = exception;
050        }
051    
052        private IOException getException() {
053          return exception;
054        }
055      }
056    
057      static private class MustSupportOption {
058        private final String name;
059        private final PropertyId pid;
060        private final Locator locator;
061    
062        MustSupportOption(String name, PropertyId pid, Locator locator) {
063          this.name = name;
064          this.pid = pid;
065          this.locator = locator;
066        }
067      }
068    
069      private class Handler extends DelegatingContentHandler implements SchemaFuture {
070        private final SchemaReceiverImpl sr;
071        private boolean hadError = false;
072        private final ErrorHandler eh;
073        private final CountingErrorHandler ceh;
074        private final Localizer localizer = new Localizer(SchemaImpl.class);
075        private Locator locator;
076        private final XmlBaseHandler xmlBaseHandler = new XmlBaseHandler();
077        private int foreignDepth = 0;
078        private Mode currentMode = null;
079        private String defaultSchemaType;
080        private Validator validator;
081        private ElementsOrAttributes match;
082        private ActionSet actions;
083        private AttributeActionSet attributeActions;
084        private String schemaUri;
085        private String schemaType;
086        private PropertyMapBuilder options;
087        private final Vector mustSupportOptions = new Vector();
088        private ModeUsage modeUsage;
089        private boolean anyNamespace;
090    
091        Handler(SchemaReceiverImpl sr) {
092          this.sr = sr;
093          this.eh = ValidateProperty.ERROR_HANDLER.get(sr.getProperties());
094          this.ceh = new CountingErrorHandler(this.eh);
095        }
096    
097        public void setDocumentLocator(Locator locator) {
098          xmlBaseHandler.setLocator(locator);
099          this.locator = locator;
100        }
101    
102        public void startDocument() throws SAXException {
103          try {
104            PropertyMapBuilder builder = new PropertyMapBuilder(sr.getProperties());
105            ValidateProperty.ERROR_HANDLER.put(builder, ceh);
106            validator = sr.getNrlSchema().createValidator(builder.toPropertyMap());
107          }
108          catch (IOException e) {
109            throw new WrappedIOException(e);
110          }
111          catch (IncorrectSchemaException e) {
112            throw new RuntimeException("internal error in RNG schema for NRL");
113          }
114          setDelegate(validator.getContentHandler());
115          if (locator != null)
116            super.setDocumentLocator(locator);
117          super.startDocument();
118        }
119    
120        public Schema getSchema() throws IncorrectSchemaException, SAXException {
121          if (validator == null || ceh.getHadErrorOrFatalError())
122            throw new IncorrectSchemaException();
123          Hashset openModes = new Hashset();
124          Hashset checkedModes = new Hashset();
125          for (Enumeration en = modeMap.keys(); en.hasMoreElements();) {
126            String modeName = (String)en.nextElement();
127            Mode mode = (Mode)modeMap.get(modeName);
128            if (!mode.isDefined())
129              error("undefined_mode", modeName, mode.getWhereUsed());
130            for (Mode tem = mode; tem != null; tem = tem.getBaseMode()) {
131              if (checkedModes.contains(tem))
132                break;
133              if (openModes.contains(tem)) {
134                error("mode_cycle", tem.getName(), tem.getWhereDefined());
135                break;
136              }
137              openModes.add(tem);
138            }
139            checkedModes.addAll(openModes);
140            openModes.clear();
141          }
142          if (hadError)
143            throw new IncorrectSchemaException();
144          return SchemaImpl.this;
145        }
146    
147        public RuntimeException unwrapException(RuntimeException e) throws SAXException, IOException, IncorrectSchemaException {
148          if (e instanceof WrappedIOException)
149            throw ((WrappedIOException)e).getException();
150          return e;
151        }
152    
153        public void startElement(String uri, String localName,
154                                 String qName, Attributes attributes)
155                throws SAXException {
156          super.startElement(uri, localName, qName, attributes);
157          xmlBaseHandler.startElement();
158          String xmlBase = attributes.getValue(WellKnownNamespaces.XML, "base");
159          if (xmlBase != null)
160            xmlBaseHandler.xmlBaseAttribute(xmlBase);
161          if (!NRL_URI.equals(uri) || foreignDepth > 0) {
162            foreignDepth++;
163            return;
164          }
165          if (ceh.getHadErrorOrFatalError())
166            return;
167          if (localName.equals("rules"))
168            parseRules(attributes);
169          else if (localName.equals("mode"))
170            parseMode(attributes);
171          else if (localName.equals("namespace"))
172            parseNamespace(attributes);
173          else if (localName.equals("anyNamespace"))
174            parseAnyNamespace(attributes);
175          else if (localName.equals("validate"))
176            parseValidate(attributes);
177          else if (localName.equals("reject"))
178            parseReject(attributes);
179          else if (localName.equals("attach"))
180            parseAttach(attributes);
181          else if (localName.equals("unwrap"))
182            parseUnwrap(attributes);
183          else if (localName.equals("allow"))
184            parseAllow(attributes);
185          else if (localName.equals("context"))
186            parseContext(attributes);
187          else if (localName.equals("option"))
188            parseOption(attributes);
189          else
190            throw new RuntimeException("unexpected element \"" + localName + "\"");
191        }
192    
193        public void endElement(String namespaceURI, String localName,
194                               String qName)
195                throws SAXException {
196          super.endElement(namespaceURI, localName, qName);
197          xmlBaseHandler.endElement();
198          if (foreignDepth > 0) {
199            foreignDepth--;
200            return;
201          }
202          if (ceh.getHadErrorOrFatalError())
203            return;
204          if (localName.equals("validate"))
205            finishValidate();
206        }
207    
208        private void parseRules(Attributes attributes) {
209          startMode = getModeAttribute(attributes, "startMode");
210          if (startMode == null) {
211            startMode = lookupCreateMode(IMPLICIT_MODE_NAME);
212            currentMode = startMode;
213            startMode.noteDefined(null);
214          }
215          startMode.noteUsed(locator);
216          if (attributesSchema) {
217            Mode wrapper = lookupCreateMode(WRAPPER_MODE_NAME);
218            ActionSet actions = new ActionSet();
219            actions.addNoResultAction(new AllowAction(new ModeUsage(startMode, startMode)));
220            wrapper.bindElement(Mode.ANY_NAMESPACE, actions);
221            wrapper.noteDefined(null);
222            startMode = wrapper;
223          }
224          defaultSchemaType = getSchemaType(attributes);
225        }
226    
227        private void parseMode(Attributes attributes) throws SAXException {
228          currentMode = getModeAttribute(attributes, "name");
229          if (currentMode.isDefined()) {
230            error("duplicate_mode", currentMode.getName());
231            error("first_mode", currentMode.getName(), currentMode.getWhereDefined());
232          }
233          else {
234            Mode base = getModeAttribute(attributes, "extends");
235            if (base != null)
236              currentMode.setBaseMode(base);
237            currentMode.noteDefined(locator);
238          }
239        }
240    
241        private void parseNamespace(Attributes attributes) throws SAXException {
242          anyNamespace = false;
243          parseRule(getNs(attributes), attributes);
244        }
245    
246        private void parseAnyNamespace(Attributes attributes) throws SAXException {
247          anyNamespace = true;
248          parseRule(Mode.ANY_NAMESPACE, attributes);
249        }
250    
251        private void parseRule(String ns, Attributes attributes) throws SAXException {
252          match = toElementsOrAttributes(attributes.getValue("", "match"),
253                                         ElementsOrAttributes.ELEMENTS);
254          if (match.containsAttributes()) {
255            attributeActions = new AttributeActionSet();
256            if (!currentMode.bindAttribute(ns, attributeActions)) {
257              if (ns.equals(Mode.ANY_NAMESPACE))
258                error("duplicate_attribute_action_any_namespace");
259              else
260                error("duplicate_attribute_action", ns);
261            }
262          }
263          if (match.containsElements()) {
264            actions = new ActionSet();
265            if (!currentMode.bindElement(ns, actions)) {
266              if (ns.equals(Mode.ANY_NAMESPACE))
267                error("duplicate_element_action_any_namespace");
268              else
269                error("duplicate_element_action", ns);
270            }
271          }
272          else
273            actions = null;
274        }
275    
276        private void parseValidate(Attributes attributes) throws SAXException {
277          schemaUri = getSchema(attributes);
278          schemaType = getSchemaType(attributes);
279          if (schemaType == null)
280            schemaType = defaultSchemaType;
281          if (actions != null)
282            modeUsage = getModeUsage(attributes);
283          else
284            modeUsage = null;
285          options = new PropertyMapBuilder();
286          mustSupportOptions.clear();
287        }
288    
289        private void finishValidate() throws SAXException {
290          try {
291            if (attributeActions != null) {
292              Schema schema = createSubSchema(true);
293              attributeActions.addSchema(schema);
294            }
295            if (actions != null) {
296              Schema schema = createSubSchema(false);
297              actions.addNoResultAction(new ValidateAction(modeUsage, schema));
298            }
299          }
300          catch (IncorrectSchemaException e) {
301            hadError = true;
302          }
303          catch (IOException e) {
304            throw new WrappedIOException(e);
305          }
306        }
307    
308        private Schema createSubSchema(boolean isAttributesSchema) throws IOException, IncorrectSchemaException, SAXException {
309          PropertyMap requestedProperties = options.toPropertyMap();
310          Schema schema = sr.createChildSchema(new InputSource(schemaUri),
311                                               schemaType,
312                                               requestedProperties,
313                                               isAttributesSchema);
314          PropertyMap actualProperties = schema.getProperties();
315          for (Enumeration en = mustSupportOptions.elements(); en.hasMoreElements();) {
316            MustSupportOption mso = (MustSupportOption)en.nextElement();
317            Object actualValue = actualProperties.get(mso.pid);
318            if (actualValue == null)
319              error("unsupported_option", mso.name, mso.locator);
320            else if (!actualValue.equals(requestedProperties.get(mso.pid)))
321              error("unsupported_option_arg", mso.name, mso.locator);
322          }
323          return schema;
324        }
325    
326        private void parseOption(Attributes attributes) throws SAXException {
327          boolean mustSupport;
328          String mustSupportValue = attributes.getValue("", "mustSupport");
329          if (mustSupportValue != null) {
330            mustSupportValue = mustSupportValue.trim();
331            mustSupport = mustSupportValue.equals("1") || mustSupportValue.equals("true");
332          }
333          else
334            mustSupport = false;
335          String name = Uri.resolve(NRL_URI, attributes.getValue("", "name"));
336          Option option = sr.getOption(name);
337          if (option == null) {
338            if (mustSupport)
339              error("unknown_option", name);
340          }
341          else {
342            String arg = attributes.getValue("", "arg");
343            try {
344              PropertyId pid = option.getPropertyId();
345              Object value = option.valueOf(arg);
346              Object oldValue = options.get(pid);
347              if (oldValue != null) {
348                value = option.combine(new Object[]{oldValue, value});
349                if (value == null)
350                  error("duplicate_option", name);
351                else
352                  options.put(pid, value);
353              }
354              else {
355                options.put(pid, value);
356                mustSupportOptions.addElement(new MustSupportOption(name, pid,
357                                                                    locator == null
358                                                                    ? null
359                                                                    : new LocatorImpl(locator)));
360              }
361            }
362            catch (OptionArgumentPresenceException e) {
363              error(arg == null ? "option_requires_argument" : "option_unexpected_argument", name);
364            }
365            catch (OptionArgumentException e) {
366              if (arg == null)
367                error("option_requires_argument", name);
368              else
369                error("option_bad_argument", name, arg);
370            }
371          }
372        }
373    
374        private void parseAttach(Attributes attributes) {
375          if (attributeActions != null)
376            attributeActions.setAttach(true);
377          if (actions != null) {
378            modeUsage = getModeUsage(attributes);
379            actions.setResultAction(new AttachAction(modeUsage));
380          }
381          else
382            modeUsage = null;
383        }
384    
385        private void parseUnwrap(Attributes attributes) {
386          if (actions != null) {
387            modeUsage = getModeUsage(attributes);
388            actions.setResultAction(new UnwrapAction(modeUsage));
389          }
390          else
391            modeUsage = null;
392        }
393    
394        private void parseAllow(Attributes attributes) {
395          if (actions != null) {
396            modeUsage = getModeUsage(attributes);
397            actions.addNoResultAction(new AllowAction(modeUsage));
398          }
399          else
400            modeUsage = null;
401        }
402    
403        private void parseReject(Attributes attributes) {
404          if (actions != null) {
405            modeUsage = getModeUsage(attributes);
406            actions.addNoResultAction(new RejectAction(modeUsage));
407          }
408          else
409            modeUsage = null;
410          if (attributeActions != null)
411            attributeActions.setReject(true);
412        }
413    
414        private void parseContext(Attributes attributes) throws SAXException {
415          if (anyNamespace) {
416            error("context_any_namespace");
417            return;
418          }
419          Mode mode = getUseMode(attributes);
420          try {
421            Vector paths = Path.parse(attributes.getValue("", "path"));
422            // XXX warning if modeUsage is null
423            if (modeUsage != null) {
424              for (int i = 0, len = paths.size(); i < len; i++) {
425                Path path = (Path)paths.elementAt(i);
426                if (!modeUsage.addContext(path.isRoot(), path.getNames(), mode))
427                  error("duplicate_path", path.toString());
428              }
429            }
430          }
431          catch (Path.ParseException e) {
432            error(e.getMessageKey());
433          }
434        }
435    
436        private String getSchema(Attributes attributes) throws SAXException {
437          String schemaUri = attributes.getValue("", "schema");
438          if (Uri.hasFragmentId(schemaUri))
439            error("schema_fragment_id");
440          return Uri.resolve(xmlBaseHandler.getBaseUri(),
441                             Uri.escapeDisallowedChars(schemaUri));
442        }
443    
444        private String getSchemaType(Attributes attributes) {
445          return attributes.getValue("", "schemaType");
446        }
447    
448        private ElementsOrAttributes toElementsOrAttributes(String value, ElementsOrAttributes defaultValue) {
449          if (value == null)
450            return defaultValue;
451          ElementsOrAttributes eoa = ElementsOrAttributes.NEITHER;
452          if (value.indexOf("elements") >= 0)
453            eoa = eoa.addElements();
454          if (value.indexOf("attributes") >= 0)
455            eoa = eoa.addAttributes();
456          return eoa;
457        }
458    
459        private ModeUsage getModeUsage(Attributes attributes) {
460          return new ModeUsage(getUseMode(attributes), currentMode);
461        }
462    
463        private Mode getUseMode(Attributes attributes) {
464          Mode mode = getModeAttribute(attributes, "useMode");
465          if (mode == null)
466            return Mode.CURRENT;
467          mode.noteUsed(locator);
468          return mode;
469        }
470    
471        private String getNs(Attributes attributes) throws SAXException {
472          String ns = attributes.getValue("", "ns");
473          if (ns != null && !Uri.isAbsolute(ns) && !ns.equals(""))
474            error("ns_absolute");
475          return ns;
476        }
477    
478        void error(String key) throws SAXException {
479          hadError = true;
480          if (eh == null)
481            return;
482          eh.error(new SAXParseException(localizer.message(key), locator));
483        }
484    
485        void error(String key, String arg) throws SAXException {
486          hadError = true;
487          if (eh == null)
488            return;
489          eh.error(new SAXParseException(localizer.message(key, arg), locator));
490        }
491    
492        void error(String key, String arg, Locator locator) throws SAXException {
493          hadError = true;
494          if (eh == null)
495            return;
496          eh.error(new SAXParseException(localizer.message(key, arg), locator));
497        }
498    
499        void error(String key, String arg1, String arg2) throws SAXException {
500          hadError = true;
501          if (eh == null)
502            return;
503          eh.error(new SAXParseException(localizer.message(key, arg1, arg2), locator));
504        }
505    
506      }
507    
508      SchemaImpl(PropertyMap properties) {
509        super(properties);
510        this.attributesSchema = properties.contains(NrlProperty.ATTRIBUTES_SCHEMA);
511        makeBuiltinMode("#allow", AllowAction.class);
512        makeBuiltinMode("#attach", AttachAction.class);
513        makeBuiltinMode("#unwrap", UnwrapAction.class);
514        defaultBaseMode = makeBuiltinMode("#reject", RejectAction.class);
515      }
516    
517      private Mode makeBuiltinMode(String name, Class cls) {
518        Mode mode = lookupCreateMode(name);
519        ActionSet actions = new ActionSet();
520        ModeUsage modeUsage = new ModeUsage(Mode.CURRENT, mode);
521        if (cls == AttachAction.class)
522          actions.setResultAction(new AttachAction(modeUsage));
523        else if (cls == AllowAction.class)
524          actions.addNoResultAction(new AllowAction(modeUsage));
525        else if (cls == UnwrapAction.class)
526          actions.setResultAction(new UnwrapAction(modeUsage));
527        else
528          actions.addNoResultAction(new RejectAction(modeUsage));
529        mode.bindElement(Mode.ANY_NAMESPACE, actions);
530        mode.noteDefined(null);
531        AttributeActionSet attributeActions = new AttributeActionSet();
532        if (attributesSchema)
533          attributeActions.setReject(true);
534        else
535          attributeActions.setAttach(true);
536        mode.bindAttribute(Mode.ANY_NAMESPACE, attributeActions);
537        return mode;
538      }
539    
540      SchemaFuture installHandlers(XMLReader in, SchemaReceiverImpl sr) {
541        Handler h = new Handler(sr);
542        in.setContentHandler(h);
543        return h;
544      }
545    
546      public Validator createValidator(PropertyMap properties) {
547        return new ValidatorImpl(startMode, properties);
548      }
549    
550      private Mode getModeAttribute(Attributes attributes, String localName) {
551        return lookupCreateMode(attributes.getValue("", localName));
552      }
553    
554      private Mode lookupCreateMode(String name) {
555        if (name == null)
556          return null;
557        name = name.trim();
558        Mode mode = (Mode)modeMap.get(name);
559        if (mode == null) {
560          mode = new Mode(name, defaultBaseMode);
561          modeMap.put(name, mode);
562        }
563        return mode;
564      }
565    
566    }