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 }