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