blob: f592a6085d0ab1a7f0f35e22e5f2d025154fbc0b [file] [log] [blame]
/*
* Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.internal.xjc.reader.dtd;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import javax.xml.namespace.QName;
import com.sun.codemodel.internal.JClass;
import com.sun.codemodel.internal.JCodeModel;
import com.sun.codemodel.internal.JDefinedClass;
import com.sun.codemodel.internal.JPackage;
import com.sun.tools.internal.xjc.AbortException;
import com.sun.tools.internal.xjc.ErrorReceiver;
import com.sun.tools.internal.xjc.Options;
import com.sun.tools.internal.xjc.model.CAttributePropertyInfo;
import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
import com.sun.tools.internal.xjc.model.CClassInfo;
import com.sun.tools.internal.xjc.model.CPropertyInfo;
import com.sun.tools.internal.xjc.model.Model;
import com.sun.tools.internal.xjc.model.TypeUse;
import com.sun.tools.internal.xjc.model.TypeUseFactory;
import com.sun.tools.internal.xjc.model.CDefaultValue;
import com.sun.tools.internal.xjc.reader.ModelChecker;
import com.sun.tools.internal.xjc.reader.Ring;
import com.sun.tools.internal.xjc.reader.dtd.bindinfo.BIAttribute;
import com.sun.tools.internal.xjc.reader.dtd.bindinfo.BIElement;
import com.sun.tools.internal.xjc.reader.dtd.bindinfo.BIInterface;
import com.sun.tools.internal.xjc.reader.dtd.bindinfo.BindInfo;
import com.sun.tools.internal.xjc.util.CodeModelClassFactory;
import com.sun.tools.internal.xjc.util.ErrorReceiverFilter;
import com.sun.xml.internal.bind.api.impl.NameConverter;
import com.sun.xml.internal.dtdparser.DTDHandlerBase;
import com.sun.xml.internal.dtdparser.DTDParser;
import com.sun.xml.internal.dtdparser.InputEntity;
import com.sun.xml.internal.xsom.XmlString;
import com.sun.istack.internal.SAXParseException2;
import org.xml.sax.EntityResolver;
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;
/**
* Parses DTD grammar along with binding information into BGM.
*
* @author
* <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
*/
public class TDTDReader extends DTDHandlerBase
{
/**
* Parses DTD grammar and a binding information into BGM.
*
* <p>
* This method is just a utility method that covers 80% of the use
* cases.
*
* @param bindingInfo
* binding information file, if any. Can be null.
*/
public static Model parse(
InputSource dtd,
InputSource bindingInfo,
ErrorReceiver errorReceiver,
Options opts) {
try {
// set up a ring
final Ring old = Ring.begin();
try {
ErrorReceiverFilter ef = new ErrorReceiverFilter(errorReceiver);
JCodeModel cm = new JCodeModel();
Model model = new Model(opts,cm,NameConverter.standard,opts.classNameAllocator,null);
Ring.add(cm);
Ring.add(model);
Ring.add(ErrorReceiver.class,ef);
TDTDReader reader = new TDTDReader( ef, opts, bindingInfo);
DTDParser parser = new DTDParser();
parser.setDtdHandler(reader);
if( opts.entityResolver!=null )
parser.setEntityResolver(opts.entityResolver);
try {
parser.parse(dtd);
} catch (SAXParseException e) {
return null; // this error was already handled by GrammarReaderController
}
Ring.get(ModelChecker.class).check();
if(ef.hadError()) return null;
else return model;
} finally {
Ring.end(old);
}
} catch (IOException e) {
errorReceiver.error(new SAXParseException2(e.getMessage(),null,e));
return null;
} catch (SAXException e) {
errorReceiver.error(new SAXParseException2(e.getMessage(),null,e));
return null;
} catch (AbortException e) {
// parsing was aborted but the error was already reported
return null;
}
}
protected TDTDReader(ErrorReceiver errorReceiver, Options opts, InputSource _bindInfo)
throws AbortException {
this.entityResolver = opts.entityResolver;
this.errorReceiver = new ErrorReceiverFilter(errorReceiver);
bindInfo = new BindInfo(model,_bindInfo, this.errorReceiver);
classFactory = new CodeModelClassFactory(errorReceiver);
}
private final EntityResolver entityResolver;
/**
* binding information.
*
* <p>
* This is always non-null even if no binding information was specified.
* (In that case, a dummy object will be provided.)
*/
final BindInfo bindInfo;
final Model model = Ring.get(Model.class);
private final CodeModelClassFactory classFactory;
private final ErrorReceiverFilter errorReceiver;
/**
* Element name to its content model definition.
*/
private final Map<String,Element> elements = new HashMap<String,Element>();
public void startDTD(InputEntity entity) throws SAXException {
}
public void endDTD() throws SAXException {
// bind them all.
// we need to know how elements are referencing each other before we do this,
// so this can be only done at the endDTD method
for( Element e : elements.values() )
e.bind();
// if there was an error by now, just abort.
if (errorReceiver.hadError())
return;
processInterfaceDeclarations();
// check XJC extensions and realize them
model.serialVersionUID = bindInfo.getSerialVersionUID();
if(model.serialVersionUID!=null)
model.serializable=true;
model.rootClass = bindInfo.getSuperClass();
model.rootInterface = bindInfo.getSuperInterface();
// TODO: do we need to reimplement them?
// // performs annotation
// Annotator.annotate(model, this);
// FieldCollisionChecker.check( model, this );
processConstructorDeclarations();
}
/** Processes interface declarations. */
private void processInterfaceDeclarations() {
Map<String,InterfaceAcceptor> fromName = new HashMap<String,InterfaceAcceptor>();
// first, create empty InterfaceItem declaration for all interfaces
Map<BIInterface,JClass> decls = new HashMap<BIInterface,JClass>();
for( BIInterface decl : bindInfo.interfaces() ) {
final JDefinedClass intf = classFactory.createInterface(
bindInfo.getTargetPackage(), decl.name(), copyLocator() );
decls.put(decl,intf);
fromName.put(decl.name(),new InterfaceAcceptor() {
public void implement(JClass c) {
intf._implements(c);
}
});
}
for( final CClassInfo ci : model.beans().values() ) {
fromName.put(ci.getName(),new InterfaceAcceptor() {
public void implement(JClass c) {
ci._implements(c);
}
});
}
// traverse the interface declarations again
// and populate its expression according to the members attribute.
for( Map.Entry<BIInterface,JClass> e : decls.entrySet() ) {
BIInterface decl = e.getKey();
JClass c = e.getValue();
for (String member : decl.members()) {
InterfaceAcceptor acc = fromName.get(member);
if (acc == null) {
// there is no such class/interface
// TODO: error location
error(decl.getSourceLocation(),
Messages.ERR_BINDINFO_NON_EXISTENT_INTERFACE_MEMBER,
member);
continue;
}
acc.implement(c);
}
}
// TODO: check the cyclic interface definition
}
private static interface InterfaceAcceptor {
void implement( JClass c );
}
JPackage getTargetPackage() {
return bindInfo.getTargetPackage();
}
/**
* Creates constructor declarations as specified in the
* binding information.
*
* <p>
* Also checks that the binding file does not contain
* declarations for non-existent elements.
*/
private void processConstructorDeclarations() {
for( BIElement decl: bindInfo.elements() ) {
Element e = elements.get(decl.name());
if(e==null) {
error(decl.getSourceLocation(),
Messages.ERR_BINDINFO_NON_EXISTENT_ELEMENT_DECLARATION,decl.name());
continue; // continue to process next declaration
}
if(!decl.isClass())
// only element-class declaration has constructor definitions
continue;
decl.declareConstructors(e.getClassInfo());
}
}
public void attributeDecl(String elementName, String attributeName, String attributeType, String[] enumeration, short attributeUse, String defaultValue) throws SAXException {
getOrCreateElement(elementName).attributes.add(
createAttribute(elementName, attributeName, attributeType, enumeration, attributeUse, defaultValue)
);
}
protected CPropertyInfo createAttribute(
String elementName, String attributeName, String attributeType,
String[] enums, short attributeUse, String defaultValue )
throws SAXException {
boolean required = attributeUse==USE_REQUIRED;
// get the attribute-property declaration
BIElement edecl = bindInfo.element(elementName);
BIAttribute decl=null;
if(edecl!=null) decl=edecl.attribute(attributeName);
String propName;
if(decl==null) propName = model.getNameConverter().toPropertyName(attributeName);
else propName = decl.getPropertyName();
QName qname = new QName("",attributeName);
// if no declaration is specified, just wrap it by
// a FieldItem and let the normalizer handle its content.
TypeUse use;
if(decl!=null && decl.getConversion()!=null)
use = decl.getConversion().getTransducer();
else
use = builtinConversions.get(attributeType);
CPropertyInfo r = new CAttributePropertyInfo(
propName, null,null/*TODO*/, copyLocator(), qname, use, null, required );
if(defaultValue!=null)
r.defaultValue = CDefaultValue.create( use, new XmlString(defaultValue) );
return r;
}
Element getOrCreateElement( String elementName ) {
Element r = elements.get(elementName);
if(r==null) {
r = new Element(this,elementName);
elements.put(elementName,r);
}
return r;
}
public void startContentModel(String elementName, short contentModelType) throws SAXException {
assert modelGroups.isEmpty();
modelGroups.push(new ModelGroup());
}
public void endContentModel(String elementName, short contentModelType) throws SAXException {
assert modelGroups.size()==1;
Term term = modelGroups.pop().wrapUp();
Element e = getOrCreateElement(elementName);
e.define( contentModelType, term, copyLocator() );
}
private final Stack<ModelGroup> modelGroups = new Stack<ModelGroup>();
public void startModelGroup() throws SAXException {
modelGroups.push(new ModelGroup());
}
public void endModelGroup(short occurence) throws SAXException {
Term t = Occurence.wrap( modelGroups.pop().wrapUp(), occurence );
modelGroups.peek().addTerm(t);
}
public void connector(short connectorType) throws SAXException {
modelGroups.peek().setKind(connectorType);
}
// TODO: for now, we just ignore all the content model specification
// and treat it as (A,B,C,....)
public void childElement(String elementName, short occurence) throws SAXException {
Element child = getOrCreateElement(elementName);
modelGroups.peek().addTerm( Occurence.wrap( child, occurence ) );
child.isReferenced = true;
}
/**
* Mutable {@link Locator} instance that points to
* the current source line.
* <p>
* Use {@link #copyLocator()} to get a immutable clone.
*/
private Locator locator;
public void setDocumentLocator(Locator loc) {
this.locator = loc;
}
/**
* Creates a snapshot of the current {@link #locator} values.
*/
private Locator copyLocator(){
return new LocatorImpl(locator);
}
//
//
// builtin datatype handling
//
//
/** Transducers for the built-in types. Read-only. */
private static final Map<String,TypeUse> builtinConversions;
static {
// list of datatypes which have built-in conversions.
// note that although xs:token and xs:normalizedString are not
// specified in the spec, they need to be here because they
// have different whitespace normalization semantics.
Map<String,TypeUse> m = new HashMap<String,TypeUse>();
m.put("CDATA", CBuiltinLeafInfo.NORMALIZED_STRING);
m.put("ENTITY", CBuiltinLeafInfo.TOKEN);
m.put("ENTITIES", CBuiltinLeafInfo.STRING.makeCollection());
m.put("NMTOKEN", CBuiltinLeafInfo.TOKEN);
m.put("NMTOKENS", CBuiltinLeafInfo.STRING.makeCollection());
m.put("ID", CBuiltinLeafInfo.ID);
m.put("IDREF", CBuiltinLeafInfo.IDREF);
m.put("IDREFS", TypeUseFactory.makeCollection(CBuiltinLeafInfo.IDREF));
m.put("ENUMERATION",CBuiltinLeafInfo.TOKEN);
builtinConversions = Collections.unmodifiableMap(m);
}
//
//
// error related utility methods
//
//
public void error(SAXParseException e) throws SAXException {
errorReceiver.error(e);
}
public void fatalError(SAXParseException e) throws SAXException {
errorReceiver.fatalError(e);
}
public void warning(SAXParseException e) throws SAXException {
errorReceiver.warning(e);
}
protected final void error( Locator loc, String prop, Object... args ) {
errorReceiver.error(loc,Messages.format(prop,args));
}
}