/* * Copyright (c) 1997, 2015, 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.xmlschema; import static com.sun.tools.internal.xjc.reader.xmlschema.BGMBuilder.getName; import java.util.Set; import javax.xml.namespace.QName; import com.sun.codemodel.internal.JJavaName; import com.sun.codemodel.internal.JPackage; import com.sun.istack.internal.NotNull; import com.sun.istack.internal.Nullable; import com.sun.tools.internal.xjc.ErrorReceiver; import com.sun.tools.internal.xjc.model.CClassInfo; import com.sun.tools.internal.xjc.model.CClassInfoParent; import com.sun.tools.internal.xjc.model.CClassRef; import com.sun.tools.internal.xjc.model.CCustomizations; import com.sun.tools.internal.xjc.model.CElement; import com.sun.tools.internal.xjc.model.CElementInfo; import com.sun.tools.internal.xjc.model.Model; import com.sun.tools.internal.xjc.reader.Ring; import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIClass; import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIGlobalBinding; import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding; import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo; import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIXSubstitutable; import com.sun.tools.internal.xjc.reader.xmlschema.ct.ComplexTypeFieldBuilder; import com.sun.tools.internal.xjc.reader.xmlschema.ct.ComplexTypeBindingMode; import com.sun.xml.internal.xsom.XSAnnotation; import com.sun.xml.internal.xsom.XSAttGroupDecl; import com.sun.xml.internal.xsom.XSAttributeDecl; import com.sun.xml.internal.xsom.XSAttributeUse; import com.sun.xml.internal.xsom.XSComplexType; import com.sun.xml.internal.xsom.XSComponent; import com.sun.xml.internal.xsom.XSContentType; import com.sun.xml.internal.xsom.XSDeclaration; import com.sun.xml.internal.xsom.XSElementDecl; import com.sun.xml.internal.xsom.XSFacet; import com.sun.xml.internal.xsom.XSIdentityConstraint; import com.sun.xml.internal.xsom.XSModelGroup; import com.sun.xml.internal.xsom.XSModelGroupDecl; import com.sun.xml.internal.xsom.XSNotation; import com.sun.xml.internal.xsom.XSParticle; import com.sun.xml.internal.xsom.XSSchema; import com.sun.xml.internal.xsom.XSSchemaSet; import com.sun.xml.internal.xsom.XSSimpleType; import com.sun.xml.internal.xsom.XSType; import com.sun.xml.internal.xsom.XSWildcard; import com.sun.xml.internal.xsom.XSXPath; import org.xml.sax.Locator; /** * Default classBinder implementation. Honors {@code } customizations * and default bindings. */ final class DefaultClassBinder implements ClassBinder { private final SimpleTypeBuilder stb = Ring.get(SimpleTypeBuilder.class); private final Model model = Ring.get(Model.class); protected final BGMBuilder builder = Ring.get(BGMBuilder.class); protected final ClassSelector selector = Ring.get(ClassSelector.class); protected final XSSchemaSet schemas = Ring.get(XSSchemaSet.class); public CElement attGroupDecl(XSAttGroupDecl decl) { return allow(decl,decl.getName()); } public CElement attributeDecl(XSAttributeDecl decl) { return allow(decl,decl.getName()); } public CElement modelGroup(XSModelGroup mgroup) { return never(); } public CElement modelGroupDecl(XSModelGroupDecl decl) { return never(); } public CElement complexType(XSComplexType type) { CElement ci = allow(type,type.getName()); if(ci!=null) return ci; // no customization is given -- do as the default binding. BindInfo bi = builder.getBindInfo(type); if(type.isGlobal()) { QName tagName = null; String className = deriveName(type); Locator loc = type.getLocator(); if(getGlobalBinding().isSimpleMode()) { // in the simple mode, we may optimize it away XSElementDecl referer = getSoleElementReferer(type); if(referer!=null && isCollapsable(referer)) { // if a global element contains // a collpsable complex type, we bind this element to a named one // and collapses element and complex type. tagName = getName(referer); className = deriveName(referer); loc = referer.getLocator(); } } // by default, global ones get their own classes. JPackage pkg = selector.getPackage(type.getTargetNamespace()); return new CClassInfo(model,pkg,className, loc,getTypeName(type),tagName,type,bi.toCustomizationList()); } else { XSElementDecl element = type.getScope(); if( element.isGlobal() && isCollapsable(element)) { if(builder.getBindInfo(element).get(BIClass.class)!=null) // the parent element was bound to a class. Don't bind this again to // cause unnecessary wrapping return null; // generate one class from element and complex type together. // this needs to be done before selector.isBound to avoid infinite recursion. // but avoid doing so when the element is mapped to a class, // which creates unnecessary classes return new CClassInfo( model, selector.getClassScope(), deriveName(element), element.getLocator(), null, getName(element), element, bi.toCustomizationList() ); } CElement parentType = selector.isBound(element,type); String className; CClassInfoParent scope; if( parentType!=null && parentType instanceof CElementInfo && ((CElementInfo)parentType).hasClass() ) { // special case where we put a nested 'Type' element scope = (CElementInfo)parentType; className = "Type"; } else { // since the parent element isn't bound to a type, merge the customizations associated to it, too. // custs = CCustomizations.merge( custs, builder.getBindInfo(type.getScope()).toCustomizationList()); className = builder.getNameConverter().toClassName(element.getName()); BISchemaBinding sb = builder.getBindInfo( type.getOwnerSchema() ).get(BISchemaBinding.class); if(sb!=null) className = sb.mangleAnonymousTypeClassName(className); scope = selector.getClassScope(); } return new CClassInfo(model, scope, className, type.getLocator(), null, null, type, bi.toCustomizationList() ); } } private QName getTypeName(XSComplexType type) { if(type.getRedefinedBy()!=null) return null; else return getName(type); } /** * Returns true if the complex type of the given element can be "optimized away" * and unified with its parent element decl to form a single class. */ private boolean isCollapsable(XSElementDecl decl) { XSType type = decl.getType(); if(!type.isComplexType()) return false; // not a complex type if(decl.getSubstitutables().size()>1 || decl.getSubstAffiliation()!=null) // because element substitution calls for a proper JAXBElement hierarchy return false; if(decl.isNillable()) // because nillable needs JAXBElement to represent correctly return false; BIXSubstitutable bixSubstitutable = builder.getBindInfo(decl).get(BIXSubstitutable.class); if(bixSubstitutable !=null) { // see https://jaxb.dev.java.net/issues/show_bug.cgi?id=289 // this customization forces non-collapsing behavior. bixSubstitutable.markAsAcknowledged(); return false; } if( getGlobalBinding().isSimpleMode() && decl.isGlobal()) { // in the simple mode, we do more aggressive optimization, and get rid of // a complex type class if it's only used once from a global element XSElementDecl referer = getSoleElementReferer(decl.getType()); if(referer!=null) { assert referer==decl; // I must be the sole referer return true; } } if(!type.isLocal() || !type.isComplexType()) return false; return true; } /** * If only one global {@link XSElementDecl} is refering to {@link XSType}, * return that element, otherwise null. */ private @Nullable XSElementDecl getSoleElementReferer(@NotNull XSType t) { Set referer = builder.getReferer(t); XSElementDecl sole = null; for (XSComponent r : referer) { if(r instanceof XSElementDecl) { XSElementDecl x = (XSElementDecl) r; if(!x.isGlobal()) // local element references can be ignored, as their names are either given // by the property, or by the JAXBElement (for things like mixed contents) continue; if(sole==null) sole=x; else return null; // more than one } else { // if another type refers to this type, that means // this type has a sub-type, so type substitution is possible now. return null; } } return sole; } public CElement elementDecl(XSElementDecl decl) { CElement r = allow(decl,decl.getName()); if(r==null) { QName tagName = getName(decl); CCustomizations custs = builder.getBindInfo(decl).toCustomizationList(); if(decl.isGlobal()) { if(isCollapsable(decl)) { // we want the returned type to be built as a complex type, // so the binding cannot be delayed. return selector.bindToType(decl.getType().asComplexType(),decl,true); } else { String className = null; if(getGlobalBinding().isGenerateElementClass()) className = deriveName(decl); // otherwise map global elements to JAXBElement CElementInfo cei = new CElementInfo( model, tagName, selector.getClassScope(), className, custs, decl.getLocator()); selector.boundElements.put(decl,cei); stb.refererStack.push(decl); // referer is element cei.initContentType( selector.bindToType(decl.getType(),decl), decl, decl.getDefaultValue() ); stb.refererStack.pop(); r = cei; } } } // have the substitution member derive from the substitution head XSElementDecl top = decl.getSubstAffiliation(); if(top!=null) { CElement topci = selector.bindToType(top,decl); if(r instanceof CClassInfo && topci instanceof CClassInfo) ((CClassInfo)r).setBaseClass((CClassInfo)topci); if (r instanceof CElementInfo && topci instanceof CElementInfo) ((CElementInfo)r).setSubstitutionHead((CElementInfo)topci); } return r; } public CClassInfo empty( XSContentType ct ) { return null; } public CClassInfo identityConstraint(XSIdentityConstraint xsIdentityConstraint) { return never(); } public CClassInfo xpath(XSXPath xsxPath) { return never(); } public CClassInfo attributeUse(XSAttributeUse use) { return never(); } public CElement simpleType(XSSimpleType type) { CElement c = allow(type,type.getName()); if(c!=null) return c; if(getGlobalBinding().isSimpleTypeSubstitution() && type.isGlobal()) { return new CClassInfo(model,selector.getClassScope(), deriveName(type), type.getLocator(), getName(type), null, type, null ); } return never(); } public CClassInfo particle(XSParticle particle) { return never(); } public CClassInfo wildcard(XSWildcard wc) { return never(); } // these methods won't be used public CClassInfo annotation(XSAnnotation annon) { assert false; return null; } public CClassInfo notation(XSNotation not) { assert false; return null; } public CClassInfo facet(XSFacet decl) { assert false; return null; } public CClassInfo schema(XSSchema schema) { assert false; return null; } /** * Makes sure that the component doesn't carry a {@link BIClass} * customization. * * @return * return value is unused. Since most of the caller needs to * return null, to make the code a little bit shorter, this * method always return null (so that the caller can always * say return never(sc);. */ private CClassInfo never() { // all we need to do here is just not to acknowledge // any class customization. Then this class customization // will be reported as an error later when we check all // unacknowledged customizations. // BIDeclaration cust=owner.getBindInfo(component).get(BIClass.NAME); // if(cust!=null) { // // error // owner.errorReporter.error( // cust.getLocation(), // "test {0}", NameGetter.get(component) ); // } return null; } /** * Checks if a component carries a customization to map it to a class. * If so, make it a class. * * @param defaultBaseName * The token which will be used as the basis of the class name * if the class name is not specified in the customization. * This is usually the name of an element declaration, and so on. * * This parameter can be null, in that case it would be an error * if a name is not given by the customization. */ private CElement allow( XSComponent component, String defaultBaseName ) { BIClass decl = null; if(component instanceof XSComplexType) { XSType complexType = (XSType)component; BIClass lastFoundRecursiveBiClass = null; if(complexType.getName() != null) { while( ! schemas.getAnyType().equals(complexType)) { BindInfo bindInfo = builder.getBindInfo(complexType); BIClass biClass = bindInfo.get(BIClass.class); if(biClass != null && "true".equals(biClass.getRecursive())) lastFoundRecursiveBiClass = biClass; complexType = complexType.getBaseType(); } } // use this as biclass for current component decl = lastFoundRecursiveBiClass; } BindInfo bindInfo = builder.getBindInfo(component); if(decl == null) { decl = bindInfo.get(BIClass.class); if(decl==null) return null; } decl.markAsAcknowledged(); // first consider binding to the class reference. String ref = decl.getExistingClassRef(); if(ref!=null) { if(!JJavaName.isFullyQualifiedClassName(ref)) { Ring.get(ErrorReceiver.class).error( decl.getLocation(), Messages.format(Messages.ERR_INCORRECT_CLASS_NAME,ref) ); // recover by ignoring @ref } else { if(component instanceof XSComplexType) { // UGLY UGLY UGLY // since we are not going to bind this complex type, we need to figure out // its binding mode without actually binding it (and also expose this otherwise // hidden mechanism into this part of the code.) // // this code is potentially dangerous as the base class might have been bound // in different ways. To be correct, we need to figure out how the content type // would have been bound, from the schema. Ring.get(ComplexTypeFieldBuilder.class).recordBindingMode( (XSComplexType)component, ComplexTypeBindingMode.NORMAL ); } return new CClassRef(model, component, decl, bindInfo.toCustomizationList() ); } } String clsName = decl.getClassName(); if(clsName==null) { // if the customiztion doesn't give us a name, derive one // from the current component. if( defaultBaseName==null ) { Ring.get(ErrorReceiver.class).error( decl.getLocation(), Messages.format(Messages.ERR_CLASS_NAME_IS_REQUIRED) ); // recover by generating a pseudo-random name defaultBaseName = "undefined"+component.hashCode(); } clsName = builder.deriveName( defaultBaseName, component ); } else { if( !JJavaName.isJavaIdentifier(clsName) ) { // not a valid Java class name Ring.get(ErrorReceiver.class).error( decl.getLocation(), Messages.format( Messages.ERR_INCORRECT_CLASS_NAME, clsName )); // recover by a dummy name clsName = "Undefined"+component.hashCode(); } } QName typeName = null; QName elementName = null; if(component instanceof XSType) { XSType t = (XSType) component; typeName = getName(t); } if (component instanceof XSElementDecl) { XSElementDecl e = (XSElementDecl) component; elementName = getName(e); } if (component instanceof XSElementDecl && !isCollapsable((XSElementDecl)component)) { XSElementDecl e = ((XSElementDecl)component); CElementInfo cei = new CElementInfo(model, elementName, selector.getClassScope(), clsName, bindInfo.toCustomizationList(), decl.getLocation() ); selector.boundElements.put(e,cei); stb.refererStack.push(component); // referer is element cei.initContentType( selector.bindToType(e.getType(),e), e,e.getDefaultValue()); stb.refererStack.pop(); return cei; // TODO: support javadoc and userSpecifiedImplClass } else { CClassInfo bt = new CClassInfo(model,selector.getClassScope(), clsName, decl.getLocation(), typeName, elementName, component, bindInfo.toCustomizationList() ); // set javadoc class comment. if(decl.getJavadoc()!=null ) bt.javadoc = decl.getJavadoc()+"\n\n"; // add extra blank lines so that the schema fragment // and user-specified javadoc would be separated // if the implClass is given, set it to ClassItem String implClass = decl.getUserSpecifiedImplClass(); if( implClass!=null ) bt.setUserSpecifiedImplClass( implClass ); return bt; } } private BIGlobalBinding getGlobalBinding() { return builder.getGlobalBinding(); } /** * Derives a name from a schema component. * Use the name of the schema component as the default name. */ private String deriveName( XSDeclaration comp ) { return builder.deriveName( comp.getName(), comp ); } /** * Derives a name from a schema component. * For complex types, we take redefinition into account when * deriving a default name. */ private String deriveName( XSComplexType comp ) { String seed = builder.deriveName( comp.getName(), comp ); int cnt = comp.getRedefinedCount(); for( ; cnt>0; cnt-- ) seed = "Original"+seed; return seed; } }