All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.tools.xjc.reader.xmlschema.ClassSelector Maven / Gradle / Ivy

There is a newer version: 4.0.5
Show newest version
/*
 * 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.
 *
 * 

* This class determines which component is mapped to (or is not mapped to) * what types. * * @author * Kohsuke Kawaguchi ([email protected]) */ 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 bindMap = new HashMap<>(); /** * UGLY HACK. *

* To avoid cyclic dependency between binding elements and types, * we need additional markers that tell which elements are definitely not bound * to a class. *

* the cyclic dependency is as follows: * elements need to bind its types first, because otherwise it can't * determine T of {@literal JAXBElement}. * OTOH, types need to know whether its parent is bound to a class to decide * which class name to use. */ /*package*/ final Map boundElements = new HashMap<>(); /** * A list of {@link Binding}s object that needs to be built. */ private final Stack bindQueue = new Stack<>(); /** * {@link CClassInfo}s that are already {@link Binding#build() built}. */ private final Set 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 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 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("

{@code\n");
        SchemaWriter sw = new SchemaWriter(out);
        sc.visit(sw);
        out.write("}
"); 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 checkedPackageNames = new HashSet<>(); /** * Gets the Java package to which classes from * this namespace should go. * *

* 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 customization if( name == null && sb!=null && sb.getPackageName()!=null ) name = sb.getPackageName(); // the JAX-RPC option goes below the 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy