| /* |
| * 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)); |
| } |
| |
| |
| } |