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 }