325N/A/*
325N/A * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Oracle designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Oracle in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
325N/A * or visit www.oracle.com if you need additional information or have any
325N/A * questions.
325N/A */
325N/A
325N/Apackage com.sun.xml.internal.xsom.impl.parser;
325N/A
325N/Aimport com.sun.xml.internal.xsom.XSDeclaration;
325N/Aimport com.sun.xml.internal.xsom.XmlString;
325N/Aimport com.sun.xml.internal.xsom.XSSimpleType;
325N/Aimport com.sun.xml.internal.xsom.impl.ForeignAttributesImpl;
325N/Aimport com.sun.xml.internal.xsom.impl.SchemaImpl;
325N/Aimport com.sun.xml.internal.xsom.impl.UName;
325N/Aimport com.sun.xml.internal.xsom.impl.Const;
325N/Aimport com.sun.xml.internal.xsom.impl.parser.state.NGCCRuntime;
325N/Aimport com.sun.xml.internal.xsom.impl.parser.state.Schema;
325N/Aimport com.sun.xml.internal.xsom.impl.util.Uri;
325N/Aimport com.sun.xml.internal.xsom.parser.AnnotationParser;
325N/Aimport org.relaxng.datatype.ValidationContext;
325N/Aimport org.xml.sax.Attributes;
325N/Aimport org.xml.sax.EntityResolver;
325N/Aimport org.xml.sax.ErrorHandler;
325N/Aimport org.xml.sax.InputSource;
325N/Aimport org.xml.sax.Locator;
325N/Aimport org.xml.sax.SAXException;
325N/Aimport org.xml.sax.SAXParseException;
325N/Aimport org.xml.sax.helpers.LocatorImpl;
325N/A
325N/Aimport java.io.IOException;
325N/Aimport java.net.URI;
325N/Aimport java.text.MessageFormat;
325N/Aimport java.util.Stack;
325N/A
325N/A/**
325N/A * NGCCRuntime extended with various utility methods for
325N/A * parsing XML Schema.
325N/A *
325N/A * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
325N/A */
325N/Apublic class NGCCRuntimeEx extends NGCCRuntime implements PatcherManager {
325N/A
325N/A /** coordinator. */
325N/A public final ParserContext parser;
325N/A
325N/A /** The schema currently being parsed. */
325N/A public SchemaImpl currentSchema;
325N/A
325N/A /** The @finalDefault value of the current schema. */
325N/A public int finalDefault = 0;
325N/A /** The @blockDefault value of the current schema. */
325N/A public int blockDefault = 0;
325N/A
325N/A /**
325N/A * The @elementFormDefault value of the current schema.
325N/A * True if local elements are qualified by default.
325N/A */
325N/A public boolean elementFormDefault = false;
325N/A
325N/A /**
325N/A * The @attributeFormDefault value of the current schema.
325N/A * True if local attributes are qualified by default.
325N/A */
325N/A public boolean attributeFormDefault = false;
325N/A
325N/A /**
325N/A * True if the current schema is in a chameleon mode.
325N/A * This changes the way QNames are interpreted.
325N/A *
325N/A * Life is very miserable with XML Schema, as you see.
325N/A */
325N/A public boolean chameleonMode = false;
325N/A
325N/A /**
325N/A * URI that identifies the schema document.
325N/A * Maybe null if the system ID is not available.
325N/A */
325N/A private String documentSystemId;
325N/A
325N/A /**
325N/A * Keep the local name of elements encountered so far.
325N/A * This information is passed to AnnotationParser as
325N/A * context information
325N/A */
325N/A private final Stack<String> elementNames = new Stack<String>();
325N/A
325N/A /**
325N/A * Points to the schema document (the parser of it) that included/imported
325N/A * this schema.
325N/A */
325N/A private final NGCCRuntimeEx referer;
325N/A
325N/A /**
325N/A * Points to the {@link SchemaDocumentImpl} that represents the
325N/A * schema document being parsed.
325N/A */
325N/A public SchemaDocumentImpl document;
325N/A
325N/A NGCCRuntimeEx( ParserContext _parser ) {
325N/A this(_parser,false,null);
325N/A }
325N/A
325N/A private NGCCRuntimeEx( ParserContext _parser, boolean chameleonMode, NGCCRuntimeEx referer ) {
325N/A this.parser = _parser;
325N/A this.chameleonMode = chameleonMode;
325N/A this.referer = referer;
325N/A
325N/A // set up the default namespace binding
325N/A currentContext = new Context("","",null);
325N/A currentContext = new Context("xml","http://www.w3.org/XML/1998/namespace",currentContext);
325N/A }
325N/A
325N/A public void checkDoubleDefError( XSDeclaration c ) throws SAXException {
325N/A if(c==null || ignorableDuplicateComponent(c)) return;
325N/A
325N/A reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION,c.getName()) );
325N/A reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION_ORIGINAL), c.getLocator() );
325N/A }
325N/A
325N/A public static boolean ignorableDuplicateComponent(XSDeclaration c) {
325N/A if(c.getTargetNamespace().equals(Const.schemaNamespace)) {
325N/A if(c instanceof XSSimpleType)
325N/A // hide artificial "double definitions" on simple types
325N/A return true;
325N/A if(c.isGlobal() && c.getName().equals("anyType"))
325N/A return true; // ditto for anyType
325N/A }
325N/A return false;
325N/A }
325N/A
325N/A
325N/A
325N/A /* registers a patcher that will run after all the parsing has finished. */
325N/A public void addPatcher( Patch patcher ) {
325N/A parser.patcherManager.addPatcher(patcher);
325N/A }
325N/A public void addErrorChecker( Patch patcher ) {
325N/A parser.patcherManager.addErrorChecker(patcher);
325N/A }
325N/A public void reportError( String msg, Locator loc ) throws SAXException {
325N/A parser.patcherManager.reportError(msg,loc);
325N/A }
325N/A public void reportError( String msg ) throws SAXException {
325N/A reportError(msg,getLocator());
325N/A }
325N/A
325N/A
325N/A /**
325N/A * Resolves relative URI found in the document.
325N/A *
325N/A * @param namespaceURI
325N/A * passed to the entity resolver.
325N/A * @param relativeUri
325N/A * value of the schemaLocation attribute. Can be null.
325N/A *
325N/A * @return
325N/A * non-null if {@link EntityResolver} returned an {@link InputSource},
325N/A * or if the relativeUri parameter seems to be pointing to something.
325N/A * Otherwise it returns null, in which case import/include should be abandoned.
325N/A */
325N/A private InputSource resolveRelativeURL( String namespaceURI, String relativeUri ) throws SAXException {
325N/A try {
325N/A String baseUri = getLocator().getSystemId();
325N/A if(baseUri==null)
325N/A // if the base URI is not available, the document system ID is
325N/A // better than nothing.
325N/A baseUri=documentSystemId;
325N/A
325N/A EntityResolver er = parser.getEntityResolver();
325N/A String systemId = null;
325N/A
325N/A if (relativeUri!=null)
325N/A systemId = Uri.resolve(baseUri,relativeUri);
325N/A
325N/A if (er!=null) {
325N/A InputSource is = er.resolveEntity(namespaceURI,systemId);
325N/A if (is == null) {
325N/A try {
325N/A String normalizedSystemId = URI.create(systemId).normalize().toASCIIString();
325N/A is = er.resolveEntity(namespaceURI,normalizedSystemId);
325N/A } catch (Exception e) {
325N/A // just ignore, this is a second try, return the fallback if this breaks
325N/A }
325N/A }
325N/A if (is != null) {
325N/A return is;
325N/A }
325N/A }
325N/A
325N/A if (systemId!=null)
325N/A return new InputSource(systemId);
325N/A else
325N/A return null;
325N/A } catch (IOException e) {
325N/A SAXParseException se = new SAXParseException(e.getMessage(),getLocator(),e);
325N/A parser.errorHandler.error(se);
325N/A return null;
325N/A }
325N/A }
325N/A
325N/A /** Includes the specified schema. */
325N/A public void includeSchema( String schemaLocation ) throws SAXException {
325N/A NGCCRuntimeEx runtime = new NGCCRuntimeEx(parser,chameleonMode,this);
325N/A runtime.currentSchema = this.currentSchema;
325N/A runtime.blockDefault = this.blockDefault;
325N/A runtime.finalDefault = this.finalDefault;
325N/A
325N/A if( schemaLocation==null ) {
325N/A SAXParseException e = new SAXParseException(
325N/A Messages.format( Messages.ERR_MISSING_SCHEMALOCATION ), getLocator() );
325N/A parser.errorHandler.fatalError(e);
325N/A throw e;
325N/A }
325N/A
325N/A runtime.parseEntity( resolveRelativeURL(null,schemaLocation),
325N/A true, currentSchema.getTargetNamespace(), getLocator() );
325N/A }
325N/A
325N/A /** Imports the specified schema. */
325N/A public void importSchema( String ns, String schemaLocation ) throws SAXException {
325N/A NGCCRuntimeEx newRuntime = new NGCCRuntimeEx(parser,false,this);
325N/A InputSource source = resolveRelativeURL(ns,schemaLocation);
325N/A if(source!=null)
325N/A newRuntime.parseEntity( source, false, ns, getLocator() );
325N/A // if source == null,
325N/A // we can't locate this document. Let's just hope that
325N/A // we already have the schema components for this schema
325N/A // or we will receive them in the future.
325N/A }
325N/A
325N/A /**
325N/A * Called when a new document is being parsed and checks
325N/A * if the document has already been parsed before.
325N/A *
325N/A * <p>
325N/A * Used to avoid recursive inclusion. Note that the same
325N/A * document will be parsed multiple times if they are for different
325N/A * target namespaces.
325N/A *
325N/A * <h2>Document Graph Model</h2>
325N/A * <p>
325N/A * The challenge we are facing here is that you have a graph of
325N/A * documents that reference each other. Each document has an unique
325N/A * URI to identify themselves, and references are done by using those.
325N/A * The graph may contain cycles.
325N/A *
325N/A * <p>
325N/A * Our goal here is to parse all the documents in the graph, without
325N/A * parsing the same document twice. This method implements this check.
325N/A *
325N/A * <p>
325N/A * One complication is the chameleon schema; a document can be parsed
325N/A * multiple times if they are under different target namespaces.
325N/A *
325N/A * <p>
325N/A * Also, note that when you resolve relative URIs in the @schemaLocation,
325N/A * their base URI is *NOT* the URI of the document.
325N/A *
325N/A * @return true if the document has already been processed and thus
325N/A * needs to be skipped.
325N/A */
325N/A public boolean hasAlreadyBeenRead() {
325N/A if( documentSystemId!=null ) {
325N/A if( documentSystemId.startsWith("file:///") )
325N/A // change file:///abc to file:/abc
325N/A // JDK File.toURL method produces the latter, but according to RFC
325N/A // I don't think that's a valid URL. Since two different ways of
325N/A // producing URLs could produce those two different forms,
325N/A // we need to canonicalize one to the other.
325N/A documentSystemId = "file:/"+documentSystemId.substring(8);
325N/A } else {
325N/A // if the system Id is not provided, we can't test the identity,
325N/A // so we have no choice but to read it.
325N/A // the newly created SchemaDocumentImpl will be unique one
325N/A }
325N/A
325N/A assert document ==null;
325N/A document = new SchemaDocumentImpl( currentSchema, documentSystemId );
325N/A
325N/A SchemaDocumentImpl existing = parser.parsedDocuments.get(document);
325N/A if(existing==null) {
325N/A parser.parsedDocuments.put(document,document);
325N/A } else {
325N/A document = existing;
325N/A }
325N/A
325N/A assert document !=null;
325N/A
325N/A if(referer!=null) {
325N/A assert referer.document !=null : "referer "+referer.documentSystemId+" has docIdentity==null";
325N/A referer.document.references.add(this.document);
325N/A this.document.referers.add(referer.document);
325N/A }
325N/A
325N/A return existing!=null;
325N/A }
325N/A
325N/A /**
325N/A * Parses the specified entity.
325N/A *
325N/A * @param importLocation
325N/A * The source location of the import/include statement.
325N/A * Used for reporting errors.
325N/A */
325N/A public void parseEntity( InputSource source, boolean includeMode, String expectedNamespace, Locator importLocation )
325N/A throws SAXException {
325N/A
325N/A documentSystemId = source.getSystemId();
325N/A try {
325N/A Schema s = new Schema(this,includeMode,expectedNamespace);
325N/A setRootHandler(s);
325N/A try {
325N/A parser.parser.parse(source,this, getErrorHandler(), parser.getEntityResolver());
325N/A } catch( IOException fnfe ) {
325N/A SAXParseException se = new SAXParseException(fnfe.toString(), importLocation, fnfe);
325N/A parser.errorHandler.warning(se);
325N/A }
325N/A } catch( SAXException e ) {
325N/A parser.setErrorFlag();
325N/A throw e;
325N/A }
325N/A }
325N/A
325N/A /**
325N/A * Creates a new instance of annotation parser.
325N/A */
325N/A public AnnotationParser createAnnotationParser() {
325N/A if(parser.getAnnotationParserFactory()==null)
325N/A return DefaultAnnotationParser.theInstance;
325N/A else
325N/A return parser.getAnnotationParserFactory().create();
325N/A }
325N/A
325N/A /**
325N/A * Gets the element name that contains the annotation element.
325N/A * This method works correctly only when called by the annotation handler.
325N/A */
325N/A public String getAnnotationContextElementName() {
325N/A return elementNames.get( elementNames.size()-2 );
325N/A }
325N/A
325N/A /** Creates a copy of the current locator object. */
325N/A public Locator copyLocator() {
325N/A return new LocatorImpl(getLocator());
325N/A }
325N/A
325N/A public ErrorHandler getErrorHandler() {
325N/A return parser.errorHandler;
325N/A }
325N/A
325N/A @Override
325N/A public void onEnterElementConsumed(String uri, String localName, String qname, Attributes atts)
325N/A throws SAXException {
325N/A super.onEnterElementConsumed(uri, localName, qname, atts);
325N/A elementNames.push(localName);
325N/A }
325N/A
325N/A @Override
325N/A public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException {
325N/A super.onLeaveElementConsumed(uri, localName, qname);
325N/A elementNames.pop();
325N/A }
325N/A
325N/A
325N/A
325N/A//
325N/A//
325N/A// ValidationContext implementation
325N/A//
325N/A//
325N/A // this object lives longer than the parser itself,
325N/A // so it's important for this object not to have any reference
325N/A // to the parser.
325N/A private static class Context implements ValidationContext {
325N/A Context( String _prefix, String _uri, Context _context ) {
325N/A this.previous = _context;
325N/A this.prefix = _prefix;
325N/A this.uri = _uri;
325N/A }
325N/A
325N/A public String resolveNamespacePrefix(String p) {
325N/A if(p.equals(prefix)) return uri;
325N/A if(previous==null) return null;
325N/A else return previous.resolveNamespacePrefix(p);
325N/A }
325N/A
325N/A private final String prefix;
325N/A private final String uri;
325N/A private final Context previous;
325N/A
325N/A // XSDLib don't use those methods, so we cut a corner here.
325N/A public String getBaseUri() { return null; }
325N/A public boolean isNotation(String arg0) { return false; }
325N/A public boolean isUnparsedEntity(String arg0) { return false; }
325N/A }
325N/A
325N/A private Context currentContext=null;
325N/A
325N/A /** Returns an immutable snapshot of the current context. */
325N/A public ValidationContext createValidationContext() {
325N/A return currentContext;
325N/A }
325N/A
325N/A public XmlString createXmlString(String value) {
325N/A if(value==null) return null;
325N/A else return new XmlString(value,createValidationContext());
325N/A }
325N/A
325N/A @Override
325N/A public void startPrefixMapping( String prefix, String uri ) throws SAXException {
325N/A super.startPrefixMapping(prefix,uri);
325N/A currentContext = new Context(prefix,uri,currentContext);
325N/A }
325N/A @Override
325N/A public void endPrefixMapping( String prefix ) throws SAXException {
325N/A super.endPrefixMapping(prefix);
325N/A currentContext = currentContext.previous;
325N/A }
325N/A
325N/A
325N/A
325N/A
325N/A
325N/A//
325N/A//
325N/A// Utility functions
325N/A//
325N/A//
325N/A
325N/A
325N/A /** Parses UName under the given context. */
325N/A public UName parseUName( String qname ) throws SAXException {
325N/A int idx = qname.indexOf(':');
325N/A if(idx<0) {
325N/A String uri = resolveNamespacePrefix("");
325N/A
325N/A // chamelon behavior. ugly...
325N/A if( uri.equals("") && chameleonMode )
325N/A uri = currentSchema.getTargetNamespace();
325N/A
325N/A // this is guaranteed to resolve
325N/A return new UName(uri,qname,qname);
325N/A } else {
325N/A String prefix = qname.substring(0,idx);
325N/A String uri = currentContext.resolveNamespacePrefix(prefix);
325N/A if(uri==null) {
325N/A // prefix failed to resolve.
325N/A reportError(Messages.format(
325N/A Messages.ERR_UNDEFINED_PREFIX,prefix));
325N/A uri="undefined"; // replace with a dummy
325N/A }
325N/A return new UName( uri, qname.substring(idx+1), qname );
325N/A }
325N/A }
325N/A
325N/A public boolean parseBoolean(String v) {
325N/A if(v==null) return false;
325N/A v=v.trim();
325N/A return v.equals("true") || v.equals("1");
325N/A }
325N/A
325N/A
325N/A @Override
325N/A protected void unexpectedX(String token) throws SAXException {
325N/A SAXParseException e = new SAXParseException(MessageFormat.format(
325N/A "Unexpected {0} appears at line {1} column {2}",
325N/A token,
325N/A getLocator().getLineNumber(),
325N/A getLocator().getColumnNumber()),
325N/A getLocator());
325N/A
325N/A parser.errorHandler.fatalError(e);
325N/A throw e; // we will abort anyway
325N/A }
325N/A
325N/A public ForeignAttributesImpl parseForeignAttributes( ForeignAttributesImpl next ) {
325N/A ForeignAttributesImpl impl = new ForeignAttributesImpl(createValidationContext(),copyLocator(),next);
325N/A
325N/A Attributes atts = getCurrentAttributes();
325N/A for( int i=0; i<atts.getLength(); i++ ) {
325N/A if(atts.getURI(i).length()>0) {
325N/A impl.addAttribute(
325N/A atts.getURI(i),
325N/A atts.getLocalName(i),
325N/A atts.getQName(i),
325N/A atts.getType(i),
325N/A atts.getValue(i)
325N/A );
325N/A }
325N/A }
325N/A
325N/A return impl;
325N/A }
325N/A
325N/A
325N/A public static final String XMLSchemaNSURI = "http://www.w3.org/2001/XMLSchema";
325N/A}