aoqi@0: /*
aoqi@0: * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0: *
aoqi@0: * This code is free software; you can redistribute it and/or modify it
aoqi@0: * under the terms of the GNU General Public License version 2 only, as
aoqi@0: * published by the Free Software Foundation. Oracle designates this
aoqi@0: * particular file as subject to the "Classpath" exception as provided
aoqi@0: * by Oracle in the LICENSE file that accompanied this code.
aoqi@0: *
aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0: * accompanied this code).
aoqi@0: *
aoqi@0: * You should have received a copy of the GNU General Public License version
aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0: *
aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0: * or visit www.oracle.com if you need additional information or have any
aoqi@0: * questions.
aoqi@0: */
aoqi@0:
aoqi@0: package com.sun.tools.internal.xjc.reader.xmlschema;
aoqi@0:
aoqi@0: import java.io.StringWriter;
aoqi@0: import java.util.HashMap;
aoqi@0: import java.util.HashSet;
aoqi@0: import java.util.Map;
aoqi@0: import java.util.Set;
aoqi@0: import java.util.Stack;
aoqi@0:
aoqi@0: import com.sun.codemodel.internal.JCodeModel;
aoqi@0: import com.sun.codemodel.internal.JJavaName;
aoqi@0: import com.sun.codemodel.internal.JPackage;
aoqi@0: import com.sun.codemodel.internal.util.JavadocEscapeWriter;
aoqi@0: import com.sun.istack.internal.NotNull;
aoqi@0: import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
aoqi@0: import com.sun.tools.internal.xjc.model.CClassInfo;
aoqi@0: import com.sun.tools.internal.xjc.model.CClassInfoParent;
aoqi@0: import com.sun.tools.internal.xjc.model.CElement;
aoqi@0: import com.sun.tools.internal.xjc.model.CElementInfo;
aoqi@0: import com.sun.tools.internal.xjc.model.CTypeInfo;
aoqi@0: import com.sun.tools.internal.xjc.model.TypeUse;
aoqi@0: import com.sun.tools.internal.xjc.model.CClass;
aoqi@0: import com.sun.tools.internal.xjc.model.CNonElement;
aoqi@0: import com.sun.tools.internal.xjc.reader.Ring;
aoqi@0: import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty;
aoqi@0: import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
aoqi@0: import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.LocalScoping;
aoqi@0: import com.sun.xml.internal.bind.v2.WellKnownNamespace;
aoqi@0: import com.sun.xml.internal.xsom.XSComplexType;
aoqi@0: import com.sun.xml.internal.xsom.XSComponent;
aoqi@0: import com.sun.xml.internal.xsom.XSDeclaration;
aoqi@0: import com.sun.xml.internal.xsom.XSElementDecl;
aoqi@0: import com.sun.xml.internal.xsom.XSSchema;
aoqi@0: import com.sun.xml.internal.xsom.XSSchemaSet;
aoqi@0: import com.sun.xml.internal.xsom.XSSimpleType;
aoqi@0: import com.sun.xml.internal.xsom.XSType;
aoqi@0: import com.sun.xml.internal.xsom.impl.util.SchemaWriter;
aoqi@0: import com.sun.xml.internal.xsom.util.ComponentNameFunction;
aoqi@0:
aoqi@0: import org.xml.sax.Locator;
aoqi@0:
aoqi@0: /**
aoqi@0: * Manages association between {@link XSComponent}s and generated
aoqi@0: * {@link CTypeInfo}s.
aoqi@0: *
aoqi@0: *
aoqi@0: * This class determines which component is mapped to (or is not mapped to)
aoqi@0: * what types.
aoqi@0: *
aoqi@0: * @author
aoqi@0: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
aoqi@0: */
aoqi@0: public final class ClassSelector extends BindingComponent {
aoqi@0: /** Center of owner classes. */
aoqi@0: private final BGMBuilder builder = Ring.get(BGMBuilder.class);
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Map from XSComponents to {@link Binding}s. Keeps track of all
aoqi@0: * content interfaces that are already built or being built.
aoqi@0: */
aoqi@0: private final Map bindMap = new HashMap();
aoqi@0:
aoqi@0: /**
aoqi@0: * UGLY HACK.
aoqi@0: *
aoqi@0: * To avoid cyclic dependency between binding elements and types,
aoqi@0: * we need additional markers that tell which elements are definitely not bound
aoqi@0: * to a class.
aoqi@0: *
aoqi@0: * the cyclic dependency is as follows:
aoqi@0: * elements need to bind its types first, because otherwise it can't
aoqi@0: * determine T of JAXBElement.
aoqi@0: * OTOH, types need to know whether its parent is bound to a class to decide
aoqi@0: * which class name to use.
aoqi@0: */
aoqi@0: /*package*/ final Map boundElements = new HashMap();
aoqi@0:
aoqi@0: /**
aoqi@0: * A list of {@link Binding}s object that needs to be built.
aoqi@0: */
aoqi@0: private final Stack bindQueue = new Stack();
aoqi@0:
aoqi@0: /**
aoqi@0: * {@link CClassInfo}s that are already {@link Binding#build() built}.
aoqi@0: */
aoqi@0: private final Set built = new HashSet();
aoqi@0:
aoqi@0: /**
aoqi@0: * Object that determines components that are mapped
aoqi@0: * to classes.
aoqi@0: */
aoqi@0: private final ClassBinder classBinder;
aoqi@0:
aoqi@0: /**
aoqi@0: * {@link CClassInfoParent}s that determines where a new class
aoqi@0: * should be created.
aoqi@0: */
aoqi@0: private final Stack classScopes = new Stack();
aoqi@0:
aoqi@0: /**
aoqi@0: * The component that is being bound to {@link #currentBean}.
aoqi@0: */
aoqi@0: private XSComponent currentRoot;
aoqi@0: /**
aoqi@0: * The bean representation we are binding right now.
aoqi@0: */
aoqi@0: private CClassInfo currentBean;
aoqi@0:
aoqi@0:
aoqi@0: private final class Binding {
aoqi@0: private final XSComponent sc;
aoqi@0: private final CTypeInfo bean;
aoqi@0:
aoqi@0: public Binding(XSComponent sc, CTypeInfo bean) {
aoqi@0: this.sc = sc;
aoqi@0: this.bean = bean;
aoqi@0: }
aoqi@0:
aoqi@0: void build() {
aoqi@0: if(!(this.bean instanceof CClassInfo))
aoqi@0: return; // no need to "build"
aoqi@0:
aoqi@0: CClassInfo bean = (CClassInfo)this.bean;
aoqi@0:
aoqi@0: if(!built.add(bean))
aoqi@0: return; // already built
aoqi@0:
aoqi@0: for( String reservedClassName : reservedClassNames ) {
aoqi@0: if( bean.getName().equals(reservedClassName) ) {
aoqi@0: getErrorReporter().error( sc.getLocator(),
aoqi@0: Messages.ERR_RESERVED_CLASS_NAME, reservedClassName );
aoqi@0: break;
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: // if this schema component is an element declaration
aoqi@0: // and it satisfies a set of conditions specified in the spec,
aoqi@0: // this class will receive a constructor.
aoqi@0: if(needValueConstructor(sc)) {
aoqi@0: // TODO: fragile. There is no guarantee that the property name
aoqi@0: // is in fact "value".
aoqi@0: bean.addConstructor("value");
aoqi@0: }
aoqi@0:
aoqi@0: if(bean.javadoc==null)
aoqi@0: addSchemaFragmentJavadoc(bean,sc);
aoqi@0:
aoqi@0: // build the body
aoqi@0: if(builder.getGlobalBinding().getFlattenClasses()==LocalScoping.NESTED)
aoqi@0: pushClassScope(bean);
aoqi@0: else
aoqi@0: pushClassScope(bean.parent());
aoqi@0: XSComponent oldRoot = currentRoot;
aoqi@0: CClassInfo oldBean = currentBean;
aoqi@0: currentRoot = sc;
aoqi@0: currentBean = bean;
aoqi@0: sc.visit(Ring.get(BindRed.class));
aoqi@0: currentBean = oldBean;
aoqi@0: currentRoot = oldRoot;
aoqi@0: popClassScope();
aoqi@0:
aoqi@0: // acknowledge property customization on this schema component,
aoqi@0: // since it is OK to have a customization at the point of declaration
aoqi@0: // even when no one is using it.
aoqi@0: BIProperty prop = builder.getBindInfo(sc).get(BIProperty.class);
aoqi@0: if(prop!=null) prop.markAsAcknowledged();
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: // should be instanciated only from BGMBuilder.
aoqi@0: public ClassSelector() {
aoqi@0: classBinder = new Abstractifier(new DefaultClassBinder());
aoqi@0: Ring.add(ClassBinder.class,classBinder);
aoqi@0:
aoqi@0: classScopes.push(null); // so that the getClassFactory method returns null
aoqi@0:
aoqi@0: XSComplexType anyType = Ring.get(XSSchemaSet.class).getComplexType(WellKnownNamespace.XML_SCHEMA,"anyType");
aoqi@0: bindMap.put(anyType,new Binding(anyType,CBuiltinLeafInfo.ANYTYPE));
aoqi@0: }
aoqi@0:
aoqi@0: /** Gets the current class scope. */
aoqi@0: public final CClassInfoParent getClassScope() {
aoqi@0: assert !classScopes.isEmpty();
aoqi@0: return classScopes.peek();
aoqi@0: }
aoqi@0:
aoqi@0: public final void pushClassScope( CClassInfoParent clsFctry ) {
aoqi@0: assert clsFctry!=null;
aoqi@0: classScopes.push(clsFctry);
aoqi@0: }
aoqi@0:
aoqi@0: public final void popClassScope() {
aoqi@0: classScopes.pop();
aoqi@0: }
aoqi@0:
aoqi@0: public XSComponent getCurrentRoot() {
aoqi@0: return currentRoot;
aoqi@0: }
aoqi@0:
aoqi@0: public CClassInfo getCurrentBean() {
aoqi@0: return currentBean;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Checks if the given component is bound to a class.
aoqi@0: */
aoqi@0: public final CElement isBound( XSElementDecl x, XSComponent referer ) {
aoqi@0: CElementInfo r = boundElements.get(x);
aoqi@0: if(r!=null)
aoqi@0: return r;
aoqi@0: return bindToType(x,referer);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Checks if the given component is being mapped to a type.
aoqi@0: * If so, build that type and return that object.
aoqi@0: * If it is not being mapped to a type item, return null.
aoqi@0: */
aoqi@0: public CTypeInfo bindToType( XSComponent sc, XSComponent referer ) {
aoqi@0: return _bindToClass(sc,referer,false);
aoqi@0: }
aoqi@0:
aoqi@0: //
aoqi@0: // some schema components are guaranteed to map to a particular CTypeInfo.
aoqi@0: // the following versions capture those constraints in the signature
aoqi@0: // and making the bindToType invocation more type safe.
aoqi@0: //
aoqi@0:
aoqi@0: public CElement bindToType( XSElementDecl e, XSComponent referer ) {
aoqi@0: return (CElement)_bindToClass(e,referer,false);
aoqi@0: }
aoqi@0:
aoqi@0: public CClass bindToType( XSComplexType t, XSComponent referer, boolean cannotBeDelayed ) {
aoqi@0: // this assumption that a complex type always binds to a ClassInfo
aoqi@0: // does not hold for xs:anyType --- our current approach of handling
aoqi@0: // this idiosynchracy is to make sure that xs:anyType doesn't use
aoqi@0: // this codepath.
aoqi@0: return (CClass)_bindToClass(t,referer,cannotBeDelayed);
aoqi@0: }
aoqi@0:
aoqi@0: public TypeUse bindToType( XSType t, XSComponent referer ) {
aoqi@0: if(t instanceof XSSimpleType) {
aoqi@0: return Ring.get(SimpleTypeBuilder.class).build((XSSimpleType)t);
aoqi@0: } else
aoqi@0: return (CNonElement)_bindToClass(t,referer,false);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * The real meat of the "bindToType" code.
aoqi@0: *
aoqi@0: * @param cannotBeDelayed
aoqi@0: * if the binding of the body of the class cannot be defered
aoqi@0: * and needs to be done immediately. If the flag is false,
aoqi@0: * the binding of the body will be done later, to avoid
aoqi@0: * cyclic binding problem.
aoqi@0: * @param referer
aoqi@0: * The component that refers to sc. This can be null,
aoqi@0: * if figuring out the referer is too hard, in which case
aoqi@0: * the error message might be less user friendly.
aoqi@0: */
aoqi@0: // TODO: consider getting rid of "cannotBeDelayed"
aoqi@0: CTypeInfo _bindToClass( @NotNull XSComponent sc, XSComponent referer, boolean cannotBeDelayed ) {
aoqi@0: // check if this class is already built.
aoqi@0: if(!bindMap.containsKey(sc)) {
aoqi@0: // craete a bind task
aoqi@0:
aoqi@0: // if this is a global declaration, make sure they will be generated
aoqi@0: // under a package.
aoqi@0: boolean isGlobal = false;
aoqi@0: if( sc instanceof XSDeclaration ) {
aoqi@0: isGlobal = ((XSDeclaration)sc).isGlobal();
aoqi@0: if( isGlobal )
aoqi@0: pushClassScope( new CClassInfoParent.Package(
aoqi@0: getPackage(((XSDeclaration)sc).getTargetNamespace())) );
aoqi@0: }
aoqi@0:
aoqi@0: // otherwise check if this component should become a class.
aoqi@0: CElement bean = sc.apply(classBinder);
aoqi@0:
aoqi@0: if( isGlobal )
aoqi@0: popClassScope();
aoqi@0:
aoqi@0: if(bean==null)
aoqi@0: return null;
aoqi@0:
aoqi@0: // can this namespace generate a class?
aoqi@0: if (bean instanceof CClassInfo) {
aoqi@0: XSSchema os = sc.getOwnerSchema();
aoqi@0: BISchemaBinding sb = builder.getBindInfo(os).get(BISchemaBinding.class);
aoqi@0: if(sb!=null && !sb.map) {
aoqi@0: // nope
aoqi@0: getErrorReporter().error(sc.getLocator(),
aoqi@0: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS, sc.apply( new ComponentNameFunction() ) );
aoqi@0: getErrorReporter().error(sb.getLocation(),
aoqi@0: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_MAP_FALSE, os.getTargetNamespace() );
aoqi@0: if(referer!=null)
aoqi@0: getErrorReporter().error(referer.getLocator(),
aoqi@0: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_REFERER, referer.apply( new ComponentNameFunction() ) );
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: queueBuild( sc, bean );
aoqi@0: }
aoqi@0:
aoqi@0: Binding bind = bindMap.get(sc);
aoqi@0: if( cannotBeDelayed )
aoqi@0: bind.build();
aoqi@0:
aoqi@0: return bind.bean;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Runs all the pending build tasks.
aoqi@0: */
aoqi@0: public void executeTasks() {
aoqi@0: while( bindQueue.size()!=0 )
aoqi@0: bindQueue.pop().build();
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Determines if the given component needs to have a value
aoqi@0: * constructor (a constructor that takes a parmater.) on ObjectFactory.
aoqi@0: */
aoqi@0: private boolean needValueConstructor( XSComponent sc ) {
aoqi@0: if(!(sc instanceof XSElementDecl)) return false;
aoqi@0:
aoqi@0: XSElementDecl decl = (XSElementDecl)sc;
aoqi@0: if(!decl.getType().isSimpleType()) return false;
aoqi@0:
aoqi@0: return true;
aoqi@0: }
aoqi@0:
aoqi@0: private static final String[] reservedClassNames = new String[]{"ObjectFactory"};
aoqi@0:
aoqi@0: public void queueBuild( XSComponent sc, CElement bean ) {
aoqi@0: // it is an error if the same component is built twice,
aoqi@0: // or the association is modified.
aoqi@0: Binding b = new Binding(sc,bean);
aoqi@0: bindQueue.push(b);
aoqi@0: Binding old = bindMap.put(sc, b);
aoqi@0: assert old==null || old.bean==bean;
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Copies a schema fragment into the javadoc of the generated class.
aoqi@0: */
aoqi@0: private void addSchemaFragmentJavadoc( CClassInfo bean, XSComponent sc ) {
aoqi@0:
aoqi@0: // first, pick it up from if any.
aoqi@0: String doc = builder.getBindInfo(sc).getDocumentation();
aoqi@0: if(doc!=null)
aoqi@0: append(bean, doc);
aoqi@0:
aoqi@0: // then the description of where this component came from
aoqi@0: Locator loc = sc.getLocator();
aoqi@0: String fileName = null;
aoqi@0: if(loc!=null) {
aoqi@0: fileName = loc.getPublicId();
aoqi@0: if(fileName==null)
aoqi@0: fileName = loc.getSystemId();
aoqi@0: }
aoqi@0: if(fileName==null) fileName="";
aoqi@0:
aoqi@0: String lineNumber=Messages.format( Messages.JAVADOC_LINE_UNKNOWN);
aoqi@0: if(loc!=null && loc.getLineNumber()!=-1)
aoqi@0: lineNumber = String.valueOf(loc.getLineNumber());
aoqi@0:
aoqi@0: String componentName = sc.apply( new ComponentNameFunction() );
aoqi@0: String jdoc = Messages.format( Messages.JAVADOC_HEADING, componentName, fileName, lineNumber );
aoqi@0: append(bean,jdoc);
aoqi@0:
aoqi@0: // then schema fragment
aoqi@0: StringWriter out = new StringWriter();
aoqi@0: out.write("\n");
aoqi@0: SchemaWriter sw = new SchemaWriter(new JavadocEscapeWriter(out));
aoqi@0: sc.visit(sw);
aoqi@0: out.write("
");
aoqi@0: append(bean,out.toString());
aoqi@0: }
aoqi@0:
aoqi@0: private void append(CClassInfo bean, String doc) {
aoqi@0: if(bean.javadoc==null)
aoqi@0: bean.javadoc = doc+'\n';
aoqi@0: else
aoqi@0: bean.javadoc += '\n'+doc+'\n';
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Set of package names that are tested (set of String
s.)
aoqi@0: *
aoqi@0: * This set is used to avoid duplicating "incorrect package name"
aoqi@0: * errors.
aoqi@0: */
aoqi@0: private static Set checkedPackageNames = new HashSet();
aoqi@0:
aoqi@0: /**
aoqi@0: * Gets the Java package to which classes from
aoqi@0: * this namespace should go.
aoqi@0: *
aoqi@0: *
aoqi@0: * Usually, the getOuterClass method should be used
aoqi@0: * to determine where to put a class.
aoqi@0: */
aoqi@0: public JPackage getPackage(String targetNamespace) {
aoqi@0: XSSchema s = Ring.get(XSSchemaSet.class).getSchema(targetNamespace);
aoqi@0:
aoqi@0: BISchemaBinding sb =
aoqi@0: builder.getBindInfo(s).get(BISchemaBinding.class);
aoqi@0: if(sb!=null) sb.markAsAcknowledged();
aoqi@0:
aoqi@0: String name = null;
aoqi@0:
aoqi@0: // "-p" takes precedence over everything else
aoqi@0: if( builder.defaultPackage1 != null )
aoqi@0: name = builder.defaultPackage1;
aoqi@0:
aoqi@0: // use the customization
aoqi@0: if( name == null && sb!=null && sb.getPackageName()!=null )
aoqi@0: name = sb.getPackageName();
aoqi@0:
aoqi@0: // the JAX-RPC option goes below the
aoqi@0: if( name == null && builder.defaultPackage2 != null )
aoqi@0: name = builder.defaultPackage2;
aoqi@0:
aoqi@0: // generate the package name from the targetNamespace
aoqi@0: if( name == null )
aoqi@0: name = builder.getNameConverter().toPackageName( targetNamespace );
aoqi@0:
aoqi@0: // hardcode a package name because the code doesn't compile
aoqi@0: // if it generated into the default java package
aoqi@0: if( name == null )
aoqi@0: name = "generated"; // the last resort
aoqi@0:
aoqi@0:
aoqi@0: // check if the package name is a valid name.
aoqi@0: if( checkedPackageNames.add(name) ) {
aoqi@0: // this is the first time we hear about this package name.
aoqi@0: if( !JJavaName.isJavaPackageName(name) )
aoqi@0: // TODO: s.getLocator() is not very helpful.
aoqi@0: // ideally, we'd like to use the locator where this package name
aoqi@0: // comes from.
aoqi@0: getErrorReporter().error(s.getLocator(),
aoqi@0: Messages.ERR_INCORRECT_PACKAGE_NAME, targetNamespace, name );
aoqi@0: }
aoqi@0:
aoqi@0: return Ring.get(JCodeModel.class)._package(name);
aoqi@0: }
aoqi@0: }