blob: b222e39c07c97893a84aaa4edf4bc4ab936ba2ed [file] [log] [blame]
/*
* Copyright (c) 1997, 2012, 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import com.sun.codemodel.internal.JCodeModel;
import com.sun.codemodel.internal.fmt.JTextFile;
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.Options;
import com.sun.tools.internal.xjc.Plugin;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
import com.sun.tools.internal.xjc.model.CClassInfoParent;
import com.sun.tools.internal.xjc.model.Model;
import com.sun.tools.internal.xjc.reader.ModelChecker;
import com.sun.tools.internal.xjc.reader.Ring;
import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDom;
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.BISerializable;
import com.sun.tools.internal.xjc.reader.xmlschema.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.bind.v2.util.XmlFactory;
import com.sun.xml.internal.xsom.XSAnnotation;
import com.sun.xml.internal.xsom.XSAttributeUse;
import com.sun.xml.internal.xsom.XSComponent;
import com.sun.xml.internal.xsom.XSDeclaration;
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.XSTerm;
import com.sun.xml.internal.xsom.XSType;
import com.sun.xml.internal.xsom.XSWildcard;
import com.sun.xml.internal.xsom.util.XSFinder;
import org.xml.sax.Locator;
/**
* Root of the XML Schema binder.
*
* <div><img src="doc-files/binding_chart.png"/></div>
*
* @author Kohsuke Kawaguchi
*/
public class BGMBuilder extends BindingComponent {
/**
* Entry point.
*/
public static Model build( XSSchemaSet _schemas, JCodeModel codeModel,
ErrorReceiver _errorReceiver, Options opts ) {
// set up a ring
final Ring old = Ring.begin();
try {
ErrorReceiverFilter ef = new ErrorReceiverFilter(_errorReceiver);
Ring.add(XSSchemaSet.class,_schemas);
Ring.add(codeModel);
Model model = new Model(opts, codeModel, null/*set later*/, opts.classNameAllocator, _schemas);
Ring.add(model);
Ring.add(ErrorReceiver.class,ef);
Ring.add(CodeModelClassFactory.class,new CodeModelClassFactory(ef));
BGMBuilder builder = new BGMBuilder(opts.defaultPackage,opts.defaultPackage2,
opts.isExtensionMode(),opts.getFieldRendererFactory(), opts.activePlugins);
builder._build();
if(ef.hadError()) return null;
else return model;
} finally {
Ring.end(old);
}
}
/**
* True if the compiler is running in the extension mode
* (as opposed to the strict conformance mode.)
*/
public final boolean inExtensionMode;
/**
* If this is non-null, this package name takes over
* all the schema customizations.
*/
public final String defaultPackage1;
/**
* If this is non-null, this package name will be
* used when no customization is specified.
*/
public final String defaultPackage2;
private final BindGreen green = Ring.get(BindGreen.class);
private final BindPurple purple = Ring.get(BindPurple.class);
public final Model model = Ring.get(Model.class);
public final FieldRendererFactory fieldRendererFactory;
/**
* Lazily computed {@link RefererFinder}.
*
* @see #getReferer
*/
private RefererFinder refFinder;
private List<Plugin> activePlugins;
protected BGMBuilder(String defaultPackage1, String defaultPackage2,
boolean _inExtensionMode, FieldRendererFactory fieldRendererFactory,
List<Plugin> activePlugins) {
this.inExtensionMode = _inExtensionMode;
this.defaultPackage1 = defaultPackage1;
this.defaultPackage2 = defaultPackage2;
this.fieldRendererFactory = fieldRendererFactory;
this.activePlugins = activePlugins;
promoteGlobalBindings();
}
private void _build() {
// do the binding
buildContents();
getClassSelector().executeTasks();
// additional error check
// Reports unused customizations to the user as errors.
Ring.get(UnusedCustomizationChecker.class).run();
Ring.get(ModelChecker.class).check();
for( Plugin ma : activePlugins )
ma.postProcessModel(model, Ring.get(ErrorReceiver.class));
}
/** List up all the global bindings. */
private void promoteGlobalBindings() {
// promote any global bindings in the schema
XSSchemaSet schemas = Ring.get(XSSchemaSet.class);
for( XSSchema s : schemas.getSchemas() ) {
BindInfo bi = getBindInfo(s);
// collect all global customizations
model.getCustomizations().addAll(bi.toCustomizationList());
BIGlobalBinding gb = bi.get(BIGlobalBinding.class);
if(gb==null)
continue;
gb.markAsAcknowledged();
if(globalBinding==null) {
globalBinding = gb;
} else {
if (!globalBinding.isEqual(gb)) { // see Issue 687 - this may happen with syntactically imported documents
// acknowledge this customization and report an error
// otherwise the user will see "customization is attached to a wrong place" error,
// which is incorrect
getErrorReporter().error( gb.getLocation(),
Messages.ERR_MULTIPLE_GLOBAL_BINDINGS);
getErrorReporter().error( globalBinding.getLocation(),
Messages.ERR_MULTIPLE_GLOBAL_BINDINGS_OTHER);
}
}
}
if( globalBinding==null ) {
// no global customization is present.
// use the default one
globalBinding = new BIGlobalBinding();
BindInfo big = new BindInfo();
big.addDecl(globalBinding);
big.setOwner(this,null);
}
// code generation mode
model.strategy = globalBinding.getCodeGenerationStrategy();
model.rootClass = globalBinding.getSuperClass();
model.rootInterface = globalBinding.getSuperInterface();
particleBinder = globalBinding.isSimpleMode() ? new ExpressionParticleBinder() : new DefaultParticleBinder();
// check XJC extensions and realize them
BISerializable serial = globalBinding.getSerializable();
if(serial!=null) {
model.serializable = true;
model.serialVersionUID = serial.uid;
}
// obtain the name conversion mode
if (globalBinding.nameConverter!=null)
model.setNameConverter(globalBinding.nameConverter);
// attach global conversions to the appropriate simple types
globalBinding.dispatchGlobalConversions(schemas);
globalBinding.errorCheck();
}
/**
* Global bindings.
*
* The empty global binding is set as the default, so that
* there will be no need to test if the value is null.
*/
private BIGlobalBinding globalBinding;
/**
* Gets the global bindings.
*/
public @NotNull BIGlobalBinding getGlobalBinding() { return globalBinding; }
private ParticleBinder particleBinder;
/**
* Gets the particle binder for this binding.
*/
public @NotNull ParticleBinder getParticleBinder() { return particleBinder; }
/**
* Name converter that implements "XML->Java name conversion"
* as specified in the spec.
*
* This object abstracts the detail that we use different name
* conversion depending on the customization.
*
* <p>
* This object should be used to perform any name conversion
* needs, instead of the JJavaName class in CodeModel.
*/
public NameConverter getNameConverter() { return model.getNameConverter(); }
/** Fill-in the contents of each classes. */
private void buildContents() {
ClassSelector cs = getClassSelector();
SimpleTypeBuilder stb = Ring.get(SimpleTypeBuilder.class);
for( XSSchema s : Ring.get(XSSchemaSet.class).getSchemas() ) {
BISchemaBinding sb = getBindInfo(s).get(BISchemaBinding.class);
if(sb!=null && !sb.map) {
sb.markAsAcknowledged();
continue; // no mapping for this package
}
getClassSelector().pushClassScope( new CClassInfoParent.Package(
getClassSelector().getPackage(s.getTargetNamespace())) );
checkMultipleSchemaBindings(s);
processPackageJavadoc(s);
populate(s.getAttGroupDecls(),s);
populate(s.getAttributeDecls(),s);
populate(s.getElementDecls(),s);
populate(s.getModelGroupDecls(),s);
// fill in typeUses
for (XSType t : s.getTypes().values()) {
stb.refererStack.push(t);
model.typeUses().put( getName(t), cs.bindToType(t,s) );
stb.refererStack.pop();
}
getClassSelector().popClassScope();
}
}
/** Reports an error if there are more than one jaxb:schemaBindings customization. */
private void checkMultipleSchemaBindings( XSSchema schema ) {
ArrayList<Locator> locations = new ArrayList<Locator>();
BindInfo bi = getBindInfo(schema);
for( BIDeclaration bid : bi ) {
if( bid.getName()==BISchemaBinding.NAME )
locations.add( bid.getLocation() );
}
if(locations.size()<=1) return; // OK
// error
getErrorReporter().error( locations.get(0),
Messages.ERR_MULTIPLE_SCHEMA_BINDINGS,
schema.getTargetNamespace() );
for( int i=1; i<locations.size(); i++ )
getErrorReporter().error( (Locator)locations.get(i),
Messages.ERR_MULTIPLE_SCHEMA_BINDINGS_LOCATION);
}
/**
* Calls {@link ClassSelector} for each item in the iterator
* to populate class items if there is any.
*/
private void populate( Map<String,? extends XSComponent> col, XSSchema schema ) {
ClassSelector cs = getClassSelector();
for( XSComponent sc : col.values() )
cs.bindToType(sc,schema);
}
/**
* Generates <code>package.html</code> if the customization
* says so.
*/
private void processPackageJavadoc( XSSchema s ) {
// look for the schema-wide customization
BISchemaBinding cust = getBindInfo(s).get(BISchemaBinding.class);
if(cust==null) return; // not present
cust.markAsAcknowledged();
if( cust.getJavadoc()==null ) return; // no javadoc customization
// produce a HTML file
JTextFile html = new JTextFile("package.html");
html.setContents(cust.getJavadoc());
getClassSelector().getPackage(s.getTargetNamespace()).addResourceFile(html);
}
/**
* Gets or creates the BindInfo object associated to a schema component.
*
* @return
* Always return a non-null valid BindInfo object.
* Even if no declaration was specified, this method creates
* a new BindInfo so that new decls can be added.
*/
public BindInfo getOrCreateBindInfo( XSComponent schemaComponent ) {
BindInfo bi = _getBindInfoReadOnly(schemaComponent);
if(bi!=null) return bi;
// XSOM is read-only, so we cannot add new annotations.
// for components that didn't have annotations,
// we maintain an external map.
bi = new BindInfo();
bi.setOwner(this,schemaComponent);
externalBindInfos.put(schemaComponent,bi);
return bi;
}
/**
* Used as a constant instance to represent the empty {@link BindInfo}.
*/
private final BindInfo emptyBindInfo = new BindInfo();
/**
* Gets the BindInfo object associated to a schema component.
*
* @return
* always return a valid {@link BindInfo} object. If none
* is specified for the given component, a dummy empty BindInfo
* will be returned.
*/
public BindInfo getBindInfo( XSComponent schemaComponent ) {
BindInfo bi = _getBindInfoReadOnly(schemaComponent);
if(bi!=null) return bi;
else return emptyBindInfo;
}
/**
* Gets the BindInfo object associated to a schema component.
*
* @return
* null if no bind info is associated to this schema component.
*/
private BindInfo _getBindInfoReadOnly( XSComponent schemaComponent ) {
BindInfo bi = externalBindInfos.get(schemaComponent);
if(bi!=null) return bi;
XSAnnotation annon = schemaComponent.getAnnotation();
if(annon!=null) {
bi = (BindInfo)annon.getAnnotation();
if(bi!=null) {
if(bi.getOwner()==null)
bi.setOwner(this,schemaComponent);
return bi;
}
}
return null;
}
/**
* A map that stores binding declarations augmented by XJC.
*/
private final Map<XSComponent,BindInfo> externalBindInfos = new HashMap<XSComponent,BindInfo>();
/**
* Gets the {@link BIDom} object that applies to the given particle.
*/
protected final BIDom getLocalDomCustomization( XSParticle p ) {
if (p == null) {
return null;
}
BIDom dom = getBindInfo(p).get(BIDom.class);
if(dom!=null) return dom;
// if not, the term might have one.
dom = getBindInfo(p.getTerm()).get(BIDom.class);
if(dom!=null) return dom;
XSTerm t = p.getTerm();
// type could also have one, in case of the dom customization
if(t.isElementDecl())
return getBindInfo(t.asElementDecl().getType()).get(BIDom.class);
// similarly the model group in a model group definition may have one.
if(t.isModelGroupDecl())
return getBindInfo(t.asModelGroupDecl().getModelGroup()).get(BIDom.class);
return null;
}
/**
* Returns true if the component should be processed by purple.
*/
private final XSFinder toPurple = new XSFinder() {
@Override
public Boolean attributeUse(XSAttributeUse use) {
// attribute use always maps to a property
return true;
}
@Override
public Boolean simpleType(XSSimpleType xsSimpleType) {
// simple type always maps to a type, hence we should take purple
return true;
}
@Override
public Boolean wildcard(XSWildcard xsWildcard) {
// attribute wildcards always maps to a property.
// element wildcards should have been processed with particle binders
return true;
}
};
/**
* If the component maps to a property, forwards to purple, otherwise to green.
*
* If the component is mapped to a type, this method needs to return true.
* See the chart at the class javadoc.
*/
public void ying( XSComponent sc, @Nullable XSComponent referer ) {
if(sc.apply(toPurple)==true || getClassSelector().bindToType(sc,referer)!=null)
sc.visit(purple);
else
sc.visit(green);
}
private Transformer identityTransformer;
/**
* Gets the shared instance of the identity transformer.
*/
public Transformer getIdentityTransformer() {
try {
if(identityTransformer==null) {
TransformerFactory tf = XmlFactory.createTransformerFactory(model.options.disableXmlSecurity);
identityTransformer = tf.newTransformer();
}
return identityTransformer;
} catch (TransformerConfigurationException e) {
throw new Error(e); // impossible
}
}
/**
* Find all types that refer to the given complex type.
*/
public Set<XSComponent> getReferer(XSType c) {
if(refFinder==null) {
refFinder = new RefererFinder();
refFinder.schemaSet(Ring.get(XSSchemaSet.class));
}
return refFinder.getReferer(c);
}
/**
* Returns the QName of the declaration.
* @return null
* if the declaration is anonymous.
*/
public static QName getName(XSDeclaration decl) {
String local = decl.getName();
if(local==null) return null;
return new QName(decl.getTargetNamespace(),local);
}
/**
* Derives a name from a schema component.
*
* This method handles prefix/suffix modification and
* XML-to-Java name conversion.
*
* @param name
* The base name. This should be things like element names
* or type names.
* @param comp
* The component from which the base name was taken.
* Used to determine how names are modified.
*/
public String deriveName( String name, XSComponent comp ) {
XSSchema owner = comp.getOwnerSchema();
name = getNameConverter().toClassName(name);
if( owner!=null ) {
BISchemaBinding sb = getBindInfo(owner).get(BISchemaBinding.class);
if(sb!=null) name = sb.mangleClassName(name,comp);
}
return name;
}
public boolean isGenerateMixedExtensions() {
if (globalBinding != null) {
return globalBinding.isGenerateMixedExtensions();
}
return false;
}
}