com.sun.xml.xsom.impl.parser.NGCCRuntimeEx Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.xml.xsom.impl.parser;
import com.sun.xml.xsom.XSDeclaration;
import com.sun.xml.xsom.XmlString;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.impl.ForeignAttributesImpl;
import com.sun.xml.xsom.impl.SchemaImpl;
import com.sun.xml.xsom.impl.UName;
import com.sun.xml.xsom.impl.Const;
import com.sun.xml.xsom.impl.parser.state.NGCCRuntime;
import com.sun.xml.xsom.impl.parser.state.Schema;
import com.sun.xml.xsom.impl.util.Uri;
import com.sun.xml.xsom.parser.AnnotationParser;
import org.relaxng.datatype.ValidationContext;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.LocatorImpl;
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.Stack;
/**
* NGCCRuntime extended with various utility methods for
* parsing XML Schema.
*
* @author Kohsuke Kawaguchi ([email protected])
*/
public class NGCCRuntimeEx extends NGCCRuntime implements PatcherManager {
/** coordinator. */
public final ParserContext parser;
/** The schema currently being parsed. */
public SchemaImpl currentSchema;
/** The @finalDefault value of the current schema. */
public int finalDefault = 0;
/** The @blockDefault value of the current schema. */
public int blockDefault = 0;
/**
* The @elementFormDefault value of the current schema.
* True if local elements are qualified by default.
*/
public boolean elementFormDefault = false;
/**
* The @attributeFormDefault value of the current schema.
* True if local attributes are qualified by default.
*/
public boolean attributeFormDefault = false;
/**
* True if the current schema is in a chameleon mode.
* This changes the way QNames are interpreted.
*
* Life is very miserable with XML Schema, as you see.
*/
public boolean chameleonMode = false;
/**
* URI that identifies the schema document.
* Maybe null if the system ID is not available.
*/
private String documentSystemId;
/**
* Keep the local name of elements encountered so far.
* This information is passed to AnnotationParser as
* context information
*/
private final Stack elementNames = new Stack();
/**
* Points to the schema document (the parser of it) that included/imported
* this schema.
*/
private final NGCCRuntimeEx referer;
/**
* Points to the {@link SchemaDocumentImpl} that represents the
* schema document being parsed.
*/
public SchemaDocumentImpl document;
NGCCRuntimeEx( ParserContext _parser ) {
this(_parser,false,null);
}
private NGCCRuntimeEx( ParserContext _parser, boolean chameleonMode, NGCCRuntimeEx referer ) {
this.parser = _parser;
this.chameleonMode = chameleonMode;
this.referer = referer;
// set up the default namespace binding
currentContext = new Context("","",null);
currentContext = new Context("xml","http://www.w3.org/XML/1998/namespace",currentContext);
}
public void checkDoubleDefError( XSDeclaration c ) throws SAXException {
if(c==null || ignorableDuplicateComponent(c)) return;
reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION,c.getName()) );
reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION_ORIGINAL), c.getLocator() );
}
public static boolean ignorableDuplicateComponent(XSDeclaration c) {
if(c.getTargetNamespace().equals(Const.schemaNamespace)) {
if(c instanceof XSSimpleType)
// hide artificial "double definitions" on simple types
return true;
if(c.isGlobal() && c.getName().equals("anyType"))
return true; // ditto for anyType
}
return false;
}
/* registers a patcher that will run after all the parsing has finished. */
public void addPatcher( Patch patcher ) {
parser.patcherManager.addPatcher(patcher);
}
public void addErrorChecker( Patch patcher ) {
parser.patcherManager.addErrorChecker(patcher);
}
public void reportError( String msg, Locator loc ) throws SAXException {
parser.patcherManager.reportError(msg,loc);
}
public void reportError( String msg ) throws SAXException {
reportError(msg,getLocator());
}
/**
* Resolves relative URI found in the document.
*
* @param namespaceURI
* passed to the entity resolver.
* @param relativeUri
* value of the schemaLocation attribute. Can be null.
*
* @return
* non-null if {@link EntityResolver} returned an {@link InputSource},
* or if the relativeUri parameter seems to be pointing to something.
* Otherwise it returns null, in which case import/include should be abandoned.
*/
private InputSource resolveRelativeURL( String namespaceURI, String relativeUri ) throws SAXException {
try {
String baseUri = getLocator().getSystemId();
if(baseUri==null)
// if the base URI is not available, the document system ID is
// better than nothing.
baseUri=documentSystemId;
EntityResolver er = parser.getEntityResolver();
String systemId = null;
if (relativeUri!=null)
systemId = Uri.resolve(baseUri,relativeUri);
if (er!=null) {
InputSource is = er.resolveEntity(namespaceURI,systemId);
if (is == null) {
try {
String normalizedSystemId = URI.create(systemId).normalize().toASCIIString();
is = er.resolveEntity(namespaceURI,normalizedSystemId);
} catch (Exception e) {
// just ignore, this is a second try, return the fallback if this breaks
}
}
if (is != null) {
return is;
}
}
if (systemId!=null)
return new InputSource(systemId);
else
return null;
} catch (IOException e) {
SAXParseException se = new SAXParseException(e.getMessage(),getLocator(),e);
parser.errorHandler.error(se);
return null;
}
}
/** Includes the specified schema. */
public void includeSchema( String schemaLocation ) throws SAXException {
NGCCRuntimeEx runtime = new NGCCRuntimeEx(parser,chameleonMode,this);
runtime.currentSchema = this.currentSchema;
runtime.blockDefault = this.blockDefault;
runtime.finalDefault = this.finalDefault;
if( schemaLocation==null ) {
SAXParseException e = new SAXParseException(
Messages.format( Messages.ERR_MISSING_SCHEMALOCATION ), getLocator() );
parser.errorHandler.fatalError(e);
throw e;
}
runtime.parseEntity( resolveRelativeURL(null,schemaLocation),
true, currentSchema.getTargetNamespace(), getLocator() );
}
/** Imports the specified schema. */
public void importSchema( String ns, String schemaLocation ) throws SAXException {
NGCCRuntimeEx newRuntime = new NGCCRuntimeEx(parser,false,this);
InputSource source = resolveRelativeURL(ns,schemaLocation);
if(source!=null)
newRuntime.parseEntity( source, false, ns, getLocator() );
// if source == null,
// we can't locate this document. Let's just hope that
// we already have the schema components for this schema
// or we will receive them in the future.
}
/**
* Called when a new document is being parsed and checks
* if the document has already been parsed before.
*
*
* Used to avoid recursive inclusion. Note that the same
* document will be parsed multiple times if they are for different
* target namespaces.
*
*
Document Graph Model
*
* The challenge we are facing here is that you have a graph of
* documents that reference each other. Each document has an unique
* URI to identify themselves, and references are done by using those.
* The graph may contain cycles.
*
*
* Our goal here is to parse all the documents in the graph, without
* parsing the same document twice. This method implements this check.
*
*
* One complication is the chameleon schema; a document can be parsed
* multiple times if they are under different target namespaces.
*
*
* Also, note that when you resolve relative URIs in the @schemaLocation,
* their base URI is *NOT* the URI of the document.
*
* @return true if the document has already been processed and thus
* needs to be skipped.
*/
public boolean hasAlreadyBeenRead() {
if( documentSystemId!=null ) {
if( documentSystemId.startsWith("file:///") )
// change file:///abc to file:/abc
// JDK File.toURL method produces the latter, but according to RFC
// I don't think that's a valid URL. Since two different ways of
// producing URLs could produce those two different forms,
// we need to canonicalize one to the other.
documentSystemId = "file:/"+documentSystemId.substring(8);
} else {
// if the system Id is not provided, we can't test the identity,
// so we have no choice but to read it.
// the newly created SchemaDocumentImpl will be unique one
}
assert document ==null;
document = new SchemaDocumentImpl( currentSchema, documentSystemId );
SchemaDocumentImpl existing = parser.parsedDocuments.get(document);
if(existing==null) {
parser.parsedDocuments.put(document,document);
} else {
document = existing;
}
assert document !=null;
if(referer!=null) {
assert referer.document !=null : "referer "+referer.documentSystemId+" has docIdentity==null";
referer.document.references.add(this.document);
this.document.referers.add(referer.document);
}
return existing!=null;
}
/**
* Parses the specified entity.
*
* @param importLocation
* The source location of the import/include statement.
* Used for reporting errors.
*/
public void parseEntity( InputSource source, boolean includeMode, String expectedNamespace, Locator importLocation )
throws SAXException {
documentSystemId = source.getSystemId();
try {
Schema s = new Schema(this,includeMode,expectedNamespace);
setRootHandler(s);
try {
parser.parser.parse(source,this, getErrorHandler(), parser.getEntityResolver());
} catch( IOException fnfe ) {
SAXParseException se = new SAXParseException(fnfe.toString(), importLocation, fnfe);
parser.errorHandler.warning(se);
}
} catch( SAXException e ) {
parser.setErrorFlag();
throw e;
}
}
/**
* Creates a new instance of annotation parser.
*/
public AnnotationParser createAnnotationParser() {
if(parser.getAnnotationParserFactory()==null)
return DefaultAnnotationParser.theInstance;
else
return parser.getAnnotationParserFactory().create();
}
/**
* Gets the element name that contains the annotation element.
* This method works correctly only when called by the annotation handler.
*/
public String getAnnotationContextElementName() {
return elementNames.get( elementNames.size()-2 );
}
/** Creates a copy of the current locator object. */
public Locator copyLocator() {
return new LocatorImpl(getLocator());
}
public ErrorHandler getErrorHandler() {
return parser.errorHandler;
}
@Override
public void onEnterElementConsumed(String uri, String localName, String qname, Attributes atts)
throws SAXException {
super.onEnterElementConsumed(uri, localName, qname, atts);
elementNames.push(localName);
}
@Override
public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException {
super.onLeaveElementConsumed(uri, localName, qname);
elementNames.pop();
}
//
//
// ValidationContext implementation
//
//
// this object lives longer than the parser itself,
// so it's important for this object not to have any reference
// to the parser.
private static class Context implements ValidationContext {
Context( String _prefix, String _uri, Context _context ) {
this.previous = _context;
this.prefix = _prefix;
this.uri = _uri;
}
public String resolveNamespacePrefix(String p) {
if(p.equals(prefix)) return uri;
if(previous==null) return null;
else return previous.resolveNamespacePrefix(p);
}
private final String prefix;
private final String uri;
private final Context previous;
// XSDLib don't use those methods, so we cut a corner here.
public String getBaseUri() { return null; }
public boolean isNotation(String arg0) { return false; }
public boolean isUnparsedEntity(String arg0) { return false; }
}
private Context currentContext=null;
/** Returns an immutable snapshot of the current context. */
public ValidationContext createValidationContext() {
return currentContext;
}
public XmlString createXmlString(String value) {
if(value==null) return null;
else return new XmlString(value,createValidationContext());
}
@Override
public void startPrefixMapping( String prefix, String uri ) throws SAXException {
super.startPrefixMapping(prefix,uri);
currentContext = new Context(prefix,uri,currentContext);
}
@Override
public void endPrefixMapping( String prefix ) throws SAXException {
super.endPrefixMapping(prefix);
currentContext = currentContext.previous;
}
//
//
// Utility functions
//
//
/** Parses UName under the given context. */
public UName parseUName( String qname ) throws SAXException {
int idx = qname.indexOf(':');
if(idx<0) {
String uri = resolveNamespacePrefix("");
// chamelon behavior. ugly...
if( uri.equals("") && chameleonMode )
uri = currentSchema.getTargetNamespace();
// this is guaranteed to resolve
return new UName(uri,qname,qname);
} else {
String prefix = qname.substring(0,idx);
String uri = currentContext.resolveNamespacePrefix(prefix);
if(uri==null) {
// prefix failed to resolve.
reportError(Messages.format(
Messages.ERR_UNDEFINED_PREFIX,prefix));
uri="undefined"; // replace with a dummy
}
return new UName( uri, qname.substring(idx+1), qname );
}
}
public boolean parseBoolean(String v) {
if(v==null) return false;
v=v.trim();
return v.equals("true") || v.equals("1");
}
@Override
protected void unexpectedX(String token) throws SAXException {
SAXParseException e = new SAXParseException(MessageFormat.format(
"Unexpected {0} appears at line {1} column {2}",
token,
getLocator().getLineNumber(),
getLocator().getColumnNumber()),
getLocator());
parser.errorHandler.fatalError(e);
throw e; // we will abort anyway
}
public ForeignAttributesImpl parseForeignAttributes( ForeignAttributesImpl next ) {
ForeignAttributesImpl impl = new ForeignAttributesImpl(createValidationContext(),copyLocator(),next);
Attributes atts = getCurrentAttributes();
for( int i=0; i0) {
impl.addAttribute(
atts.getURI(i),
atts.getLocalName(i),
atts.getQName(i),
atts.getType(i),
atts.getValue(i)
);
}
}
return impl;
}
public static final String XMLSchemaNSURI = "http://www.w3.org/2001/XMLSchema";
}