001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.xsom.impl.parser;
027:
028: import com.sun.xml.internal.xsom.XSDeclaration;
029: import com.sun.xml.internal.xsom.XmlString;
030: import com.sun.xml.internal.xsom.impl.ForeignAttributesImpl;
031: import com.sun.xml.internal.xsom.impl.SchemaImpl;
032: import com.sun.xml.internal.xsom.impl.UName;
033: import com.sun.xml.internal.xsom.impl.parser.state.NGCCRuntime;
034: import com.sun.xml.internal.xsom.impl.parser.state.Schema;
035: import com.sun.xml.internal.xsom.impl.util.Uri;
036: import com.sun.xml.internal.xsom.parser.AnnotationParser;
037: import org.relaxng.datatype.ValidationContext;
038: import org.xml.sax.Attributes;
039: import org.xml.sax.EntityResolver;
040: import org.xml.sax.ErrorHandler;
041: import org.xml.sax.InputSource;
042: import org.xml.sax.Locator;
043: import org.xml.sax.SAXException;
044: import org.xml.sax.SAXParseException;
045: import org.xml.sax.helpers.LocatorImpl;
046:
047: import java.io.IOException;
048: import java.text.MessageFormat;
049: import java.util.Stack;
050:
051: /**
052: * NGCCRuntime extended with various utility methods for
053: * parsing XML Schema.
054: *
055: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
056: */
057: public class NGCCRuntimeEx extends NGCCRuntime implements
058: PatcherManager {
059:
060: /** coordinator. */
061: public final ParserContext parser;
062:
063: /** The schema currently being parsed. */
064: public SchemaImpl currentSchema;
065:
066: /** The @finalDefault value of the current schema. */
067: public int finalDefault = 0;
068: /** The @blockDefault value of the current schema. */
069: public int blockDefault = 0;
070:
071: /**
072: * The @elementFormDefault value of the current schema.
073: * True if local elements are qualified by default.
074: */
075: public boolean elementFormDefault = false;
076:
077: /**
078: * The @attributeFormDefault value of the current schema.
079: * True if local attributes are qualified by default.
080: */
081: public boolean attributeFormDefault = false;
082:
083: /**
084: * True if the current schema is in a chameleon mode.
085: * This changes the way QNames are interpreted.
086: *
087: * Life is very miserable with XML Schema, as you see.
088: */
089: public boolean chameleonMode = false;
090:
091: /**
092: * URI that identifies the schema document.
093: * Maybe null if the system ID is not available.
094: */
095: private String documentSystemId;
096:
097: /**
098: * Keep the local name of elements encountered so far.
099: * This information is passed to AnnotationParser as
100: * context information
101: */
102: private final Stack<String> elementNames = new Stack<String>();
103:
104: /**
105: * Points to the schema document (the parser of it) that included/imported
106: * this schema.
107: */
108: private final NGCCRuntimeEx referer;
109:
110: /**
111: * Points to the {@link SchemaDocumentImpl} that represents the
112: * schema document being parsed.
113: */
114: public SchemaDocumentImpl document;
115:
116: NGCCRuntimeEx(ParserContext _parser) {
117: this (_parser, false, null);
118: }
119:
120: private NGCCRuntimeEx(ParserContext _parser, boolean chameleonMode,
121: NGCCRuntimeEx referer) {
122: this .parser = _parser;
123: this .chameleonMode = chameleonMode;
124: this .referer = referer;
125:
126: // set up the default namespace binding
127: currentContext = new Context("", "", null);
128: currentContext = new Context("xml",
129: "http://www.w3.org/XML/1998/namespace", currentContext);
130: }
131:
132: public void checkDoubleDefError(XSDeclaration c)
133: throws SAXException {
134: if (c == null)
135: return;
136: reportError(Messages.format(Messages.ERR_DOUBLE_DEFINITION, c
137: .getName()));
138: reportError(Messages
139: .format(Messages.ERR_DOUBLE_DEFINITION_ORIGINAL), c
140: .getLocator());
141: }
142:
143: /* registers a patcher that will run after all the parsing has finished. */
144: public void addPatcher(Patch patcher) {
145: parser.patcherManager.addPatcher(patcher);
146: }
147:
148: public void reportError(String msg, Locator loc)
149: throws SAXException {
150: parser.patcherManager.reportError(msg, loc);
151: }
152:
153: public void reportError(String msg) throws SAXException {
154: reportError(msg, getLocator());
155: }
156:
157: /**
158: * Resolves relative URI found in the document.
159: *
160: * @param namespaceURI
161: * passed to the entity resolver.
162: * @param relativeUri
163: * value of the schemaLocation attribute. Can be null.
164: *
165: * @return
166: * non-null if {@link EntityResolver} returned an {@link InputSource},
167: * or if the relativeUri parameter seems to be pointing to something.
168: * Otherwise it returns null, in which case import/include should be abandoned.
169: */
170: private InputSource resolveRelativeURL(String namespaceURI,
171: String relativeUri) throws SAXException {
172: try {
173: String baseUri = getLocator().getSystemId();
174: if (baseUri == null)
175: // if the base URI is not available, the document system ID is
176: // better than nothing.
177: baseUri = documentSystemId;
178:
179: String systemId = null;
180: if (relativeUri != null)
181: systemId = Uri.resolve(baseUri, relativeUri);
182:
183: EntityResolver er = parser.getEntityResolver();
184: if (er != null) {
185: InputSource is = er.resolveEntity(namespaceURI,
186: systemId);
187: if (is != null)
188: return is;
189: }
190:
191: if (systemId != null)
192: return new InputSource(systemId);
193: else
194: return null;
195: } catch (IOException e) {
196: SAXParseException se = new SAXParseException(
197: e.getMessage(), getLocator(), e);
198: parser.errorHandler.error(se);
199: return null;
200: }
201: }
202:
203: /** Includes the specified schema. */
204: public void includeSchema(String schemaLocation)
205: throws SAXException {
206: NGCCRuntimeEx runtime = new NGCCRuntimeEx(parser,
207: chameleonMode, this );
208: runtime.currentSchema = this .currentSchema;
209: runtime.blockDefault = this .blockDefault;
210: runtime.finalDefault = this .finalDefault;
211:
212: if (schemaLocation == null) {
213: SAXParseException e = new SAXParseException(Messages
214: .format(Messages.ERR_MISSING_SCHEMALOCATION),
215: getLocator());
216: parser.errorHandler.fatalError(e);
217: throw e;
218: }
219:
220: runtime.parseEntity(resolveRelativeURL(null, schemaLocation),
221: true, currentSchema.getTargetNamespace(), getLocator());
222: }
223:
224: /** Imports the specified schema. */
225: public void importSchema(String ns, String schemaLocation)
226: throws SAXException {
227: NGCCRuntimeEx newRuntime = new NGCCRuntimeEx(parser, false,
228: this );
229: InputSource source = resolveRelativeURL(ns, schemaLocation);
230: if (source != null)
231: newRuntime.parseEntity(source, false, ns, getLocator());
232: // if source == null,
233: // we can't locate this document. Let's just hope that
234: // we already have the schema components for this schema
235: // or we will receive them in the future.
236: }
237:
238: /**
239: * Called when a new document is being parsed and checks
240: * if the document has already been parsed before.
241: *
242: * <p>
243: * Used to avoid recursive inclusion. Note that the same
244: * document will be parsed multiple times if they are for different
245: * target namespaces.
246: *
247: * <h2>Document Graph Model</h2>
248: * <p>
249: * The challenge we are facing here is that you have a graph of
250: * documents that reference each other. Each document has an unique
251: * URI to identify themselves, and references are done by using those.
252: * The graph may contain cycles.
253: *
254: * <p>
255: * Our goal here is to parse all the documents in the graph, without
256: * parsing the same document twice. This method implements this check.
257: *
258: * <p>
259: * One complication is the chameleon schema; a document can be parsed
260: * multiple times if they are under different target namespaces.
261: *
262: * <p>
263: * Also, note that when you resolve relative URIs in the @schemaLocation,
264: * their base URI is *NOT* the URI of the document.
265: *
266: * @return true if the document has already been processed and thus
267: * needs to be skipped.
268: */
269: public boolean hasAlreadyBeenRead() {
270: if (documentSystemId != null) {
271: if (documentSystemId.startsWith("file:///"))
272: // change file:///abc to file:/abc
273: // JDK File.toURL method produces the latter, but according to RFC
274: // I don't think that's a valid URL. Since two different ways of
275: // producing URLs could produce those two different forms,
276: // we need to canonicalize one to the other.
277: documentSystemId = "file:/"
278: + documentSystemId.substring(8);
279: } else {
280: // if the system Id is not provided, we can't test the identity,
281: // so we have no choice but to read it.
282: // the newly created SchemaDocumentImpl will be unique one
283: }
284:
285: assert document == null;
286: document = new SchemaDocumentImpl(currentSchema,
287: documentSystemId);
288:
289: SchemaDocumentImpl existing = parser.parsedDocuments
290: .get(document);
291: if (existing == null) {
292: parser.parsedDocuments.put(document, document);
293: } else {
294: document = existing;
295: }
296:
297: assert document != null;
298:
299: if (referer != null) {
300: assert referer.document != null : "referer "
301: + referer.documentSystemId
302: + " has docIdentity==null";
303: referer.document.references.add(this .document);
304: this .document.referers.add(referer.document);
305: }
306:
307: return existing != null;
308: }
309:
310: /**
311: * Parses the specified entity.
312: *
313: * @param importLocation
314: * The source location of the import/include statement.
315: * Used for reporting errors.
316: */
317: public void parseEntity(InputSource source, boolean includeMode,
318: String expectedNamespace, Locator importLocation)
319: throws SAXException {
320:
321: documentSystemId = source.getSystemId();
322: // System.out.println("parsing "+baseUri);
323:
324: try {
325: Schema s = new Schema(this , includeMode, expectedNamespace);
326: setRootHandler(s);
327:
328: try {
329: parser.parser.parse(source, this , getErrorHandler(),
330: parser.getEntityResolver());
331: } catch (IOException e) {
332: SAXParseException se = new SAXParseException(e
333: .toString(), importLocation, e);
334: parser.errorHandler.fatalError(se);
335: throw se;
336: }
337: } catch (SAXException e) {
338: parser.setErrorFlag();
339: throw e;
340: }
341: }
342:
343: /**
344: * Creates a new instance of annotation parser.
345: */
346: public AnnotationParser createAnnotationParser() {
347: if (parser.getAnnotationParserFactory() == null)
348: return DefaultAnnotationParser.theInstance;
349: else
350: return parser.getAnnotationParserFactory().create();
351: }
352:
353: /**
354: * Gets the element name that contains the annotation element.
355: * This method works correctly only when called by the annotation handler.
356: */
357: public String getAnnotationContextElementName() {
358: return elementNames.get(elementNames.size() - 2);
359: }
360:
361: /** Creates a copy of the current locator object. */
362: public Locator copyLocator() {
363: return new LocatorImpl(getLocator());
364: }
365:
366: public ErrorHandler getErrorHandler() {
367: return parser.errorHandler;
368: }
369:
370: public void onEnterElementConsumed(String uri, String localName,
371: String qname, Attributes atts) throws SAXException {
372: super .onEnterElementConsumed(uri, localName, qname, atts);
373: elementNames.push(localName);
374: }
375:
376: public void onLeaveElementConsumed(String uri, String localName,
377: String qname) throws SAXException {
378: super .onLeaveElementConsumed(uri, localName, qname);
379: elementNames.pop();
380: }
381:
382: //
383: //
384: // ValidationContext implementation
385: //
386: //
387: // this object lives longer than the parser itself,
388: // so it's important for this object not to have any reference
389: // to the parser.
390: private static class Context implements ValidationContext {
391: Context(String _prefix, String _uri, Context _context) {
392: this .previous = _context;
393: this .prefix = _prefix;
394: this .uri = _uri;
395: }
396:
397: public String resolveNamespacePrefix(String p) {
398: if (p.equals(prefix))
399: return uri;
400: if (previous == null)
401: return null;
402: else
403: return previous.resolveNamespacePrefix(p);
404: }
405:
406: private final String prefix;
407: private final String uri;
408: private final Context previous;
409:
410: // XSDLib don't use those methods, so we cut a corner here.
411: public String getBaseUri() {
412: return null;
413: }
414:
415: public boolean isNotation(String arg0) {
416: return false;
417: }
418:
419: public boolean isUnparsedEntity(String arg0) {
420: return false;
421: }
422: }
423:
424: private Context currentContext = null;
425:
426: /** Returns an immutable snapshot of the current context. */
427: public ValidationContext createValidationContext() {
428: return currentContext;
429: }
430:
431: public XmlString createXmlString(String value) {
432: if (value == null)
433: return null;
434: else
435: return new XmlString(value, createValidationContext());
436: }
437:
438: public void startPrefixMapping(String prefix, String uri)
439: throws SAXException {
440: super .startPrefixMapping(prefix, uri);
441: currentContext = new Context(prefix, uri, currentContext);
442: }
443:
444: public void endPrefixMapping(String prefix) throws SAXException {
445: super .endPrefixMapping(prefix);
446: currentContext = currentContext.previous;
447: }
448:
449: //
450: //
451: // Utility functions
452: //
453: //
454:
455: /** Parses UName under the given context. */
456: public UName parseUName(String qname) throws SAXException {
457: int idx = qname.indexOf(':');
458: if (idx < 0) {
459: String uri = resolveNamespacePrefix("");
460:
461: // chamelon behavior. ugly...
462: if (uri.equals("") && chameleonMode)
463: uri = currentSchema.getTargetNamespace();
464:
465: // this is guaranteed to resolve
466: return new UName(uri, qname, qname);
467: } else {
468: String prefix = qname.substring(0, idx);
469: String uri = currentContext.resolveNamespacePrefix(prefix);
470: if (uri == null) {
471: // prefix failed to resolve.
472: reportError(Messages.format(
473: Messages.ERR_UNDEFINED_PREFIX, prefix));
474: uri = "undefined"; // replace with a dummy
475: }
476: return new UName(uri, qname.substring(idx + 1), qname);
477: }
478: }
479:
480: public boolean parseBoolean(String v) {
481: if (v == null)
482: return false;
483: v = v.trim();
484: return v.equals("true") || v.equals("1");
485: }
486:
487: protected void unexpectedX(String token) throws SAXException {
488: SAXParseException e = new SAXParseException(
489: MessageFormat
490: .format(
491: "Unexpected {0} appears at line {1} column {2}",
492: token, getLocator().getLineNumber(),
493: getLocator().getColumnNumber()),
494: getLocator());
495:
496: parser.errorHandler.fatalError(e);
497: throw e; // we will abort anyway
498: }
499:
500: public ForeignAttributesImpl parseForeignAttributes(
501: ForeignAttributesImpl next) {
502: ForeignAttributesImpl impl = new ForeignAttributesImpl(
503: createValidationContext(), copyLocator(), next);
504:
505: Attributes atts = getCurrentAttributes();
506: for (int i = 0; i < atts.getLength(); i++) {
507: if (atts.getURI(i).length() > 0) {
508: impl.addAttribute(atts.getURI(i), atts.getLocalName(i),
509: atts.getQName(i), atts.getType(i), atts
510: .getValue(i));
511: }
512: }
513:
514: return impl;
515: }
516:
517: public static final String XMLSchemaNSURI = "http://www.w3.org/2001/XMLSchema";
518: }
|