com.sun.tools.xjc.model.Model Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2021 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.model;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JPackage;
import com.sun.tools.xjc.ErrorReceiver;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.api.ClassNameAllocator;
import com.sun.tools.xjc.generator.bean.BeanGenerator;
import com.sun.tools.xjc.generator.bean.ImplStructureStrategy;
import com.sun.tools.xjc.model.nav.NClass;
import com.sun.tools.xjc.model.nav.NType;
import com.sun.tools.xjc.model.nav.NavigatorImpl;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.reader.xmlschema.Messages;
import com.sun.tools.xjc.util.ErrorReceiverFilter;
import cn.lzgabel.jaxb.core.api.impl.NameConverter;
import cn.lzgabel.jaxb.core.v2.model.core.Ref;
import cn.lzgabel.jaxb.core.v2.model.core.TypeInfoSet;
import cn.lzgabel.jaxb.core.v2.model.nav.Navigator;
import cn.lzgabel.jaxb.core.v2.util.FlattenIterator;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSSchemaSet;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.LocatorImpl;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlNsForm;
import jakarta.xml.bind.annotation.XmlTransient;
import javax.xml.namespace.QName;
import javax.xml.transform.Result;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Root of the object model that represents the code that needs to be generated.
*
*
* A {@link Model} is a schema language neutral representation of the
* result of a schema parsing. The back-end then works against this model
* to turn this into a series of Java source code.
*
* @author Kohsuke Kawaguchi
*/
public final class Model implements TypeInfoSet, CCustomizable {
/**
* Generated beans.
*/
private final Map beans = new LinkedHashMap<>();
/**
* Generated enums.
*/
private final Map enums = new LinkedHashMap<>();
/**
* The element mappings.
*/
private final Map> elementMappings =
new LinkedHashMap<>();
private final Iterable extends CElementInfo> allElements =
new Iterable() {
@Override
public Iterator iterator() {
return new FlattenIterator<>(elementMappings.values());
}
};
/**
* {@link TypeUse}s for all named types.
*
* I really don't want to promote the notion of a 'type' in any place except in the XML Schema code,
* but this needs to be exposed for JAX-RPC. A reference to a named XML type will be converted into
* a reference to a Java type with annotations.
*/
private final Map typeUses = new LinkedHashMap<>();
/**
* {@link NameConverter} to be used.
*/
private NameConverter nameConverter;
/**
* Single linked list that connects all {@link CCustomizations} that belong to this model.
*
* @see CCustomizations#next
*/
/*package*/ CCustomizations customizations;
/**
* This field controls the generation of package level annotations for s2j
*/
private boolean packageLevelAnnotations = true;
/**
* If this model was built from XML Schema, this field
* stores the root object of the parse schema model.
* Otherwise null.
*
* @since 2.1.1
*/
public final XSSchemaSet schemaComponent;
private CCustomizations globalCustomizations = new CCustomizations();
/**
* @param nc
* Usually this should be set in the constructor, but we do allow this parameter
* to be initially null, and then set later.
* @param schemaComponent
* The source schema model, if this is built from XSD.
*/
public Model( Options opts, JCodeModel cm, NameConverter nc, ClassNameAllocator allocator, XSSchemaSet schemaComponent ) {
this.options = opts;
this.codeModel = cm;
this.nameConverter = nc;
this.defaultSymbolSpace = new SymbolSpace(codeModel);
defaultSymbolSpace.setType(codeModel.ref(Object.class));
elementMappings.put(null, new LinkedHashMap<>());
if(opts.automaticNameConflictResolution)
allocator = new AutoClassNameAllocator(allocator);
this.allocator = new ClassNameAllocatorWrapper(allocator);
this.schemaComponent = schemaComponent;
this.globalCustomizations.setParent(this, this);
}
public void setNameConverter(NameConverter nameConverter) {
assert this.nameConverter==null;
assert nameConverter!=null;
this.nameConverter = nameConverter;
}
/**
* Gets the name converter that shall be used to parse XML names into Java names.
*/
public final NameConverter getNameConverter() {
return nameConverter;
}
public boolean isPackageLevelAnnotations() {
return packageLevelAnnotations;
}
public void setPackageLevelAnnotations(boolean packageLevelAnnotations) {
this.packageLevelAnnotations = packageLevelAnnotations;
}
/**
* This model uses this code model exclusively.
*/
@XmlTransient
public final JCodeModel codeModel;
/**
* Command-line options used for building this model.
*/
public final Options options;
/**
* True to generate serializable classes.
*/
@XmlAttribute
public boolean serializable;
/**
* serial version UID to be generated.
*
* null if not to generate serialVersionUID field.
*/
@XmlAttribute
public Long serialVersionUID;
/**
* If non-null, all the generated classes should eventually derive from this class.
*/
@XmlTransient
public JClass rootClass;
/**
* If non-null, all the generated interfaces should eventually derive from this interface.
*/
@XmlTransient
public JClass rootInterface;
/**
* Specifies the code generation strategy.
* Must not be null.
*/
public ImplStructureStrategy strategy = ImplStructureStrategy.BEAN_ONLY;
/**
* This allocator has the final say on deciding the class name.
* Must not be null.
*
*
* Model classes are responsible for using the allocator.
* This allocator interaction should be transparent to the user/builder
* of the model.
*/
/*package*/ final ClassNameAllocatorWrapper allocator;
/**
* Default ID/IDREF symbol space. Any ID/IDREF without explicit
* reference to a symbol space is assumed to use this default
* symbol space.
*/
@XmlTransient
public final SymbolSpace defaultSymbolSpace;
/** All the defined {@link SymbolSpace}s keyed by their name. */
private final Map symbolSpaces = new HashMap<>();
public SymbolSpace getSymbolSpace( String name ) {
SymbolSpace ss = symbolSpaces.get(name);
if(ss==null)
symbolSpaces.put(name,ss=new SymbolSpace(codeModel));
return ss;
}
/**
* Fully-generate the source code into the given model.
*
* @return
* null if there was any errors. Otherwise it returns a valid
* {@link Outline} object, which captures how the model objects
* are mapped to the generated source code.
*
* Add-ons can use those information to further augment the generated
* source code.
*/
public Outline generateCode(Options opt,ErrorReceiver receiver) {
ErrorReceiverFilter ehf = new ErrorReceiverFilter(receiver);
// run extensions // moved to BGMBuilder._build() - issue with hyperjaxb3
// for( Plugin ma : opt.activePlugins )
// ma.postProcessModel(this,ehf);
Outline o = BeanGenerator.generate(this, ehf);
try {// run extensions
for( Plugin ma : opt.activePlugins )
ma.run(o,opt,ehf);
} catch (SAXException e) {
// fatal error. error should have been reported
return null;
}
// check for unused plug-in customizations.
// these can be only checked after the plug-ins run, so it's here.
// the JAXB bindings are checked by XMLSchema's builder.
Set check = new HashSet<>();
for( CCustomizations c=customizations; c!=null; c=c.next ) {
if(!check.add(c)) {
throw new AssertionError(); // detect a loop
}
for (CPluginCustomization p : c) {
if(!p.isAcknowledged()) {
ehf.error(
p.locator,
Messages.format(
Messages.ERR_UNACKNOWLEDGED_CUSTOMIZATION,
p.element.getNodeName()
));
ehf.error(
c.getOwner().getLocator(),
Messages.format(
Messages.ERR_UNACKNOWLEDGED_CUSTOMIZATION_LOCATION));
}
}
}
if(ehf.hadError())
o = null;
return o;
}
/**
* Represents the "top-level binding".
*
*
* This is used to support the use of a schema inside WSDL.
* For XML Schema, the top-level binding is a map from
* global element declarations to its representation class.
*
*
* For other schema languages, it should follow the appendices in
* WSDL (but in practice no one would use WSDL with a schema language
* other than XML Schema, so it doesn't really matter.)
*
*
* This needs to be filled by the front-end.
*/
public final Map createTopLevelBindings() {
Map r = new HashMap<>();
for( CClassInfo b : beans().values() ) {
if(b.isElement())
r.put(b.getElementName(),b);
}
return r;
}
@Override
public Navigator getNavigator() {
return NavigatorImpl.theInstance;
}
@Override
public CNonElement getTypeInfo(NType type) {
CBuiltinLeafInfo leaf = CBuiltinLeafInfo.LEAVES.get(type);
if(leaf!=null) return leaf;
return getClassInfo(getNavigator().asDecl(type));
}
@Override
public CBuiltinLeafInfo getAnyTypeInfo() {
return CBuiltinLeafInfo.ANYTYPE;
}
@Override
public CNonElement getTypeInfo(Ref ref) {
// TODO: handle XmlValueList
assert !ref.valueList;
return getTypeInfo(ref.type);
}
@Override
public Map beans() {
return beans;
}
@Override
public Map enums() {
return enums;
}
public Map typeUses() {
return typeUses;
}
/**
* No array mapping generation for XJC.
*/
@Override
public Map arrays() {
return Collections.emptyMap();
}
@Override
public Map builtins() {
return CBuiltinLeafInfo.LEAVES;
}
@Override
public CClassInfo getClassInfo(NClass t) {
return beans.get(t);
}
@Override
public CElementInfo getElementInfo(NClass scope,QName name) {
Map m = elementMappings.get(scope);
if(m!=null) {
CElementInfo r = m.get(name);
if(r!=null) return r;
}
return elementMappings.get(null).get(name);
}
@Override
public Map getElementMappings(NClass scope) {
return elementMappings.get(scope);
}
@Override
public Iterable extends CElementInfo> getAllElements() {
return allElements;
}
/**
* @deprecated
* Always return null. Perhaps you are interested in {@link #schemaComponent}?
*/
@Deprecated
@Override
public XSComponent getSchemaComponent() {
return null;
}
/**
* @deprecated
* No line number available for the "root" component.
*/
@Deprecated
@Override
public Locator getLocator() {
LocatorImpl r = new LocatorImpl();
r.setLineNumber(-1);
r.setColumnNumber(-1);
return r;
}
/**
* Gets the global customizations.
*/
@Override
public CCustomizations getCustomizations() {
return globalCustomizations;
}
/**
* Not implemented in the compile-time model.
*/
@Override
public Map getXmlNs(String namespaceUri) {
return Collections.emptyMap();
}
@Override
public Map getSchemaLocations() {
return Collections.emptyMap();
}
@Override
public XmlNsForm getElementFormDefault(String nsUri) {
throw new UnsupportedOperationException();
}
@Override
public XmlNsForm getAttributeFormDefault(String nsUri) {
throw new UnsupportedOperationException();
}
@Override
public void dump(Result out) {
// TODO
throw new UnsupportedOperationException();
}
/*package*/ void add( CEnumLeafInfo e ) {
enums.put( e.getClazz(), e );
}
/*package*/ void add( CClassInfo ci ) {
beans.put( ci.getClazz(), ci );
}
/*package*/ void add( CElementInfo ei ) {
NClass clazz = null;
if(ei.getScope()!=null)
clazz = ei.getScope().getClazz();
Map m = elementMappings.get(clazz);
if(m==null)
elementMappings.put(clazz, m = new LinkedHashMap<>());
m.put(ei.getElementName(),ei);
}
private final Map cache = new HashMap<>();
public CClassInfoParent.Package getPackage(JPackage pkg) {
CClassInfoParent.Package r = cache.get(pkg);
if(r==null)
cache.put(pkg,r=new CClassInfoParent.Package(pkg));
return r;
}
/*package*/ static final Locator EMPTY_LOCATOR;
static {
LocatorImpl l = new LocatorImpl();
l.setColumnNumber(-1);
l.setLineNumber(-1);
EMPTY_LOCATOR = l;
}
}