package com.sun.tools.internal.xjc.generator.bean;
import static com.sun.tools.internal.xjc.outline.Aspect.EXPOSED;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttachmentRef;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.namespace.QName;
import com.sun.codemodel.internal.ClassType;
import com.sun.codemodel.internal.JAnnotatable;
import com.sun.codemodel.internal.JClass;
import com.sun.codemodel.internal.JClassAlreadyExistsException;
import com.sun.codemodel.internal.JClassContainer;
import com.sun.codemodel.internal.JCodeModel;
import com.sun.codemodel.internal.JDefinedClass;
import com.sun.codemodel.internal.JEnumConstant;
import com.sun.codemodel.internal.JExpr;
import com.sun.codemodel.internal.JExpression;
import com.sun.codemodel.internal.JFieldVar;
import com.sun.codemodel.internal.JForEach;
import com.sun.codemodel.internal.JInvocation;
import com.sun.codemodel.internal.JJavaName;
import com.sun.codemodel.internal.JMethod;
import com.sun.codemodel.internal.JMod;
import com.sun.codemodel.internal.JPackage;
import com.sun.codemodel.internal.JType;
import com.sun.codemodel.internal.JVar;
import com.sun.codemodel.internal.fmt.JStaticJavaFile;
import com.sun.tools.internal.xjc.AbortException;
import com.sun.tools.internal.xjc.ErrorReceiver;
import com.sun.tools.internal.xjc.api.SpecVersion;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlAnyAttributeWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlEnumValueWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlEnumWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlJavaTypeAdapterWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlMimeTypeWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlRootElementWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlSeeAlsoWriter;
import com.sun.tools.internal.xjc.generator.annotation.spec.XmlTypeWriter;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRenderer;
import com.sun.tools.internal.xjc.model.CAdapter;
import com.sun.tools.internal.xjc.model.CAttributePropertyInfo;
import com.sun.tools.internal.xjc.model.CClassInfo;
import com.sun.tools.internal.xjc.model.CClassInfoParent;
import com.sun.tools.internal.xjc.model.CElementInfo;
import com.sun.tools.internal.xjc.model.CEnumConstant;
import com.sun.tools.internal.xjc.model.CEnumLeafInfo;
import com.sun.tools.internal.xjc.model.CPropertyInfo;
import com.sun.tools.internal.xjc.model.CTypeRef;
import com.sun.tools.internal.xjc.model.Model;
import com.sun.tools.internal.xjc.model.CClassRef;
import com.sun.tools.internal.xjc.outline.Aspect;
import com.sun.tools.internal.xjc.outline.ClassOutline;
import com.sun.tools.internal.xjc.outline.EnumConstantOutline;
import com.sun.tools.internal.xjc.outline.EnumOutline;
import com.sun.tools.internal.xjc.outline.FieldOutline;
import com.sun.tools.internal.xjc.outline.Outline;
import com.sun.tools.internal.xjc.outline.PackageOutline;
import com.sun.tools.internal.xjc.util.CodeModelClassFactory;
import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapterMarker;
import com.sun.xml.internal.xsom.XmlString;
import com.sun.istack.internal.NotNull;
import com.sun.tools.internal.xjc.model.CReferencePropertyInfo;
public final class BeanGenerator implements Outline {
private static final String JAXB_PACKAGE = "java.xml.bind";
private final CodeModelClassFactory codeModelClassFactory;
private final ErrorReceiver errorReceiver;
private final Map<JPackage, PackageOutlineImpl> packageContexts = new LinkedHashMap<JPackage, PackageOutlineImpl>();
private final Map<CClassInfo, ClassOutlineImpl> classes = new LinkedHashMap<CClassInfo, ClassOutlineImpl>();
private final Map<CEnumLeafInfo, EnumOutline> enums = new LinkedHashMap<CEnumLeafInfo, EnumOutline>();
private final Map<Class, JClass> generatedRuntime = new LinkedHashMap<Class, JClass>();
private final Model model;
private final JCodeModel codeModel;
private final Map<CPropertyInfo, FieldOutline> fields = new LinkedHashMap<CPropertyInfo, FieldOutline>();
final Map<CElementInfo, ElementOutlineImpl> elements = new LinkedHashMap<CElementInfo, ElementOutlineImpl>();
public static Outline generate(Model model, ErrorReceiver _errorReceiver) {
try {
return new BeanGenerator(model, _errorReceiver);
} catch (AbortException e) {
return null;
}
}
private BeanGenerator(Model _model, ErrorReceiver _errorReceiver) {
this.model = _model;
this.codeModel = model.codeModel;
this.errorReceiver = _errorReceiver;
this.codeModelClassFactory = new CodeModelClassFactory(errorReceiver);
for (CEnumLeafInfo p : model.enums().values()) {
enums.put(p, generateEnumDef(p));
}
JPackage[] packages = getUsedPackages(EXPOSED);
for (JPackage pkg : packages) {
getPackageContext(pkg);
}
for (CClassInfo bean : model.beans().values()) {
getClazz(bean);
}
for (PackageOutlineImpl p : packageContexts.values()) {
p.calcDefaultValues();
}
JClass OBJECT = codeModel.ref(Object.class);
for (ClassOutlineImpl cc : getClasses()) {
CClassInfo superClass = cc.target.getBaseClass();
if (superClass != null) {
model.strategy._extends(cc, getClazz(superClass));
} else {
CClassRef refSuperClass = cc.target.getRefBaseClass();
if (refSuperClass != null) {
cc.implClass._extends(refSuperClass.toType(this, EXPOSED));
} else {
if (model.rootClass != null && cc.implClass._extends().equals(OBJECT)) {
cc.implClass._extends(model.rootClass);
}
if (model.rootInterface != null) {
cc.ref._implements(model.rootInterface);
}
}
}
if (model.serializable) {
cc.implClass._implements(Serializable.class);
if (model.serialVersionUID != null) {
cc.implClass.field(
JMod.PRIVATE | JMod.STATIC | JMod.FINAL,
codeModel.LONG,
"serialVersionUID",
JExpr.lit(model.serialVersionUID));
}
}
CClassInfoParent base = cc.target.parent();
if ((base != null) && (base instanceof CClassInfo)) {
String pkg = base.getOwnerPackage().name();
String shortName = base.fullName().substring(base.fullName().indexOf(pkg)+pkg.length()+1);
if (cc.target.shortName.equals(shortName)) {
getErrorReceiver().error(cc.target.getLocator(), Messages.ERR_KEYNAME_COLLISION.format(shortName));
}
}
}
for (ClassOutlineImpl co : getClasses()) {
generateClassBody(co);
}
for (EnumOutline eo : enums.values()) {
generateEnumBody(eo);
}
for (CElementInfo ei : model.getAllElements()) {
getPackageContext(ei._package()).objectFactoryGenerator().populate(ei);
}
if (model.options.getModuleName() != null) {
codeModel._prepareModuleInfo(model.options.getModuleName(), JAXB_PACKAGE);
}
if (model.options.debugMode) {
generateClassList();
}
}
@SuppressWarnings("CallToThreadDumpStack")
private void generateClassList() {
try {
JDefinedClass jc = codeModel.rootPackage()._class("JAXBDebug");
JMethod m = jc.method(JMod.PUBLIC | JMod.STATIC, JAXBContext.class, "createContext");
JVar $classLoader = m.param(ClassLoader.class, "classLoader");
m._throws(JAXBException.class);
JInvocation inv = codeModel.ref(JAXBContext.class).staticInvoke("newInstance");
m.body()._return(inv);
switch (model.strategy) {
case INTF_AND_IMPL: {
StringBuilder buf = new StringBuilder();
for (PackageOutlineImpl po : packageContexts.values()) {
if (buf.length() > 0) {
buf.append(':');
}
buf.append(po._package().name());
}
inv.arg(buf.toString()).arg($classLoader);
break;
}
case BEAN_ONLY:
for (ClassOutlineImpl cc : getClasses()) {
inv.arg(cc.implRef.dotclass());
}
for (PackageOutlineImpl po : packageContexts.values()) {
inv.arg(po.objectFactory().dotclass());
}
break;
default:
throw new IllegalStateException();
}
} catch (JClassAlreadyExistsException e) {
e.printStackTrace();
}
}
public Model getModel() {
return model;
}
public JCodeModel getCodeModel() {
return codeModel;
}
public JClassContainer getContainer(CClassInfoParent parent, Aspect aspect) {
CClassInfoParent.Visitor<JClassContainer> v;
switch (aspect) {
case EXPOSED:
v = exposedContainerBuilder;
break;
case IMPLEMENTATION:
v = implContainerBuilder;
break;
default:
assert false;
throw new IllegalStateException();
}
return parent.accept(v);
}
public final JType resolve(CTypeRef ref, Aspect a) {
return ref.getTarget().getType().toType(this, a);
}
private final CClassInfoParent.Visitor<JClassContainer> exposedContainerBuilder =
new CClassInfoParent.Visitor<JClassContainer>() {
public JClassContainer onBean(CClassInfo bean) {
return getClazz(bean).ref;
}
public JClassContainer onElement(CElementInfo element) {
return getElement(element).implClass;
}
public JClassContainer onPackage(JPackage pkg) {
return model.strategy.getPackage(pkg, EXPOSED);
}
};
private final CClassInfoParent.Visitor<JClassContainer> implContainerBuilder =
new CClassInfoParent.Visitor<JClassContainer>() {
public JClassContainer onBean(CClassInfo bean) {
return getClazz(bean).implClass;
}
public JClassContainer onElement(CElementInfo element) {
return getElement(element).implClass;
}
public JClassContainer onPackage(JPackage pkg) {
return model.strategy.getPackage(pkg, Aspect.IMPLEMENTATION);
}
};
public final JPackage[] getUsedPackages(Aspect aspect) {
Set<JPackage> s = new TreeSet<JPackage>();
for (CClassInfo bean : model.beans().values()) {
JClassContainer cont = getContainer(bean.parent(), aspect);
if (cont.isPackage()) {
s.add((JPackage) cont);
}
}
for (CElementInfo e : model.getElementMappings(null).values()) {
s.add(e._package());
}
return s.toArray(new JPackage[s.size()]);
}
public ErrorReceiver getErrorReceiver() {
return errorReceiver;
}
public CodeModelClassFactory getClassFactory() {
return codeModelClassFactory;
}
public PackageOutlineImpl getPackageContext(JPackage p) {
PackageOutlineImpl r = packageContexts.get(p);
if (r == null) {
r = new PackageOutlineImpl(this, model, p);
packageContexts.put(p, r);
}
return r;
}
private ClassOutlineImpl generateClassDef(CClassInfo bean) {
ImplStructureStrategy.Result r = model.strategy.createClasses(this, bean);
JClass implRef;
if (bean.getUserSpecifiedImplClass() != null) {
JDefinedClass usr;
try {
usr = codeModel._class(bean.getUserSpecifiedImplClass());
usr.hide();
} catch (JClassAlreadyExistsException e) {
usr = e.getExistingClass();
}
usr._extends(r.implementation);
implRef = usr;
} else {
implRef = r.implementation;
}
return new ClassOutlineImpl(this, bean, r.exposed, r.implementation, implRef);
}
public Collection<ClassOutlineImpl> getClasses() {
assert model.beans().size() == classes.size();
return classes.values();
}
public ClassOutlineImpl getClazz(CClassInfo bean) {
ClassOutlineImpl r = classes.get(bean);
if (r == null) {
classes.put(bean, r = generateClassDef(bean));
}
return r;
}
public ElementOutlineImpl getElement(CElementInfo ei) {
ElementOutlineImpl def = elements.get(ei);
if (def == null && ei.hasClass()) {
def = new ElementOutlineImpl(this, ei);
}
return def;
}
public EnumOutline getEnum(CEnumLeafInfo eli) {
return enums.get(eli);
}
public Collection<EnumOutline> getEnums() {
return enums.values();
}
public Iterable<? extends PackageOutline> getAllPackageContexts() {
return packageContexts.values();
}
public FieldOutline getField(CPropertyInfo prop) {
return fields.get(prop);
}
private void generateClassBody(ClassOutlineImpl cc) {
CClassInfo target = cc.target;
String mostUsedNamespaceURI = cc._package().getMostUsedNamespaceURI();
XmlTypeWriter xtw = cc.implClass.annotate2(XmlTypeWriter.class);
writeTypeName(cc.target.getTypeName(), xtw, mostUsedNamespaceURI);
if (model.options.target.isLaterThan(SpecVersion.V2_1)) {
Iterator<CClassInfo> subclasses = cc.target.listSubclasses();
if (subclasses.hasNext()) {
XmlSeeAlsoWriter saw = cc.implClass.annotate2(XmlSeeAlsoWriter.class);
while (subclasses.hasNext()) {
CClassInfo s = subclasses.next();
saw.value(getClazz(s).implRef);
}
}
}
if (target.isElement()) {
String namespaceURI = target.getElementName().getNamespaceURI();
String localPart = target.getElementName().getLocalPart();
XmlRootElementWriter xrew = cc.implClass.annotate2(XmlRootElementWriter.class);
xrew.name(localPart);
if (!namespaceURI.equals(mostUsedNamespaceURI))
{
xrew.namespace(namespaceURI);
}
}
if (target.isOrdered()) {
for (CPropertyInfo p : target.getProperties()) {
if (!(p instanceof CAttributePropertyInfo)) {
if (!((p instanceof CReferencePropertyInfo)
&& ((CReferencePropertyInfo) p).isDummy())) {
xtw.propOrder(p.getName(false));
}
}
}
} else {
xtw.getAnnotationUse().paramArray("propOrder");
}
for (CPropertyInfo prop : target.getProperties()) {
generateFieldDecl(cc, prop);
}
if (target.declaresAttributeWildcard()) {
generateAttributeWildcard(cc);
}
cc.ref.javadoc().append(target.javadoc);
cc._package().objectFactoryGenerator().populate(cc);
}
private void writeTypeName(QName typeName, XmlTypeWriter xtw, String mostUsedNamespaceURI) {
if (typeName == null) {
xtw.name("");
} else {
xtw.name(typeName.getLocalPart());
final String typeNameURI = typeName.getNamespaceURI();
if (!typeNameURI.equals(mostUsedNamespaceURI))
{
xtw.namespace(typeNameURI);
}
}
}
private void generateAttributeWildcard(ClassOutlineImpl cc) {
String FIELD_NAME = "otherAttributes";
String METHOD_SEED = model.getNameConverter().toClassName(FIELD_NAME);
JClass mapType = codeModel.ref(Map.class).narrow(QName.class, String.class);
JClass mapImpl = codeModel.ref(HashMap.class).narrow(QName.class, String.class);
JFieldVar $ref = cc.implClass.field(JMod.PRIVATE,
mapType, FIELD_NAME, JExpr._new(mapImpl));
$ref.annotate2(XmlAnyAttributeWriter.class);
MethodWriter writer = cc.createMethodWriter();
JMethod $get = writer.declareMethod(mapType, "get" + METHOD_SEED);
$get.javadoc().append(
"Gets a map that contains attributes that aren't bound to any typed property on this class.\n\n"
+ "<p>\n"
+ "the map is keyed by the name of the attribute and \n"
+ "the value is the string value of the attribute.\n"
+ "\n"
+ "the map returned by this method is live, and you can add new attribute\n"
+ "by updating the map directly. Because of this design, there's no setter.\n");
$get.javadoc().addReturn().append("always non-null");
$get.body()._return($ref);
}
private EnumOutline generateEnumDef(CEnumLeafInfo e) {
JDefinedClass type;
type = getClassFactory().createClass(
getContainer(e.parent, EXPOSED), e.shortName, e.getLocator(), ClassType.ENUM);
type.javadoc().append(e.javadoc);
return new EnumOutline(e, type) {
@Override
public
@NotNull
Outline parent() {
return BeanGenerator.this;
}
};
}
private void generateEnumBody(EnumOutline eo) {
JDefinedClass type = eo.clazz;
CEnumLeafInfo e = eo.target;
XmlTypeWriter xtw = type.annotate2(XmlTypeWriter.class);
writeTypeName(e.getTypeName(), xtw,
eo._package().getMostUsedNamespaceURI());
JCodeModel cModel = model.codeModel;
JType baseExposedType = e.base.toType(this, EXPOSED).unboxify();
JType baseImplType = e.base.toType(this, Aspect.IMPLEMENTATION).unboxify();
XmlEnumWriter xew = type.annotate2(XmlEnumWriter.class);
xew.value(baseExposedType);
boolean needsValue = e.needsValueField();
Set<String> enumFieldNames = new HashSet<String>();
for (CEnumConstant mem : e.members) {
String constName = mem.getName();
if (!JJavaName.isJavaIdentifier(constName)) {
getErrorReceiver().error(e.getLocator(),
Messages.ERR_UNUSABLE_NAME.format(mem.getLexicalValue(), constName));
}
if (!enumFieldNames.add(constName)) {
getErrorReceiver().error(e.getLocator(), Messages.ERR_NAME_COLLISION.format(constName));
}
JEnumConstant constRef = type.enumConstant(constName);
if (needsValue) {
constRef.arg(e.base.createConstant(this, new XmlString(mem.getLexicalValue())));
}
if (!mem.getLexicalValue().equals(constName)) {
constRef.annotate2(XmlEnumValueWriter.class).value(mem.getLexicalValue());
}
if (mem.javadoc != null) {
constRef.javadoc().append(mem.javadoc);
}
eo.constants.add(new EnumConstantOutline(mem, constRef) {
});
}
if (needsValue) {
JFieldVar $value = type.field(JMod.PRIVATE | JMod.FINAL, baseExposedType, "value");
type.method(JMod.PUBLIC, baseExposedType, "value").body()._return($value);
{
JMethod m = type.constructor(0);
m.body().assign($value, m.param(baseImplType, "v"));
}
{
JMethod m = type.method(JMod.PUBLIC | JMod.STATIC, type, "fromValue");
JVar $v = m.param(baseExposedType, "v");
JForEach fe = m.body().forEach(type, "c", type.staticInvoke("values"));
JExpression eq;
if (baseExposedType.isPrimitive()) {
eq = fe.var().ref($value).eq($v);
} else {
eq = fe.var().ref($value).invoke("equals").arg($v);
}
fe.body()._if(eq)._then()._return(fe.var());
JInvocation ex = JExpr._new(cModel.ref(IllegalArgumentException.class));
JExpression strForm;
if (baseExposedType.isPrimitive()) {
strForm = cModel.ref(String.class).staticInvoke("valueOf").arg($v);
} else if (baseExposedType == cModel.ref(String.class)) {
strForm = $v;
} else {
strForm = $v.invoke("toString");
}
m.body()._throw(ex.arg(strForm));
}
} else {
type.method(JMod.PUBLIC, String.class, "value").body()._return(JExpr.invoke("name"));
JMethod m = type.method(JMod.PUBLIC | JMod.STATIC, type, "fromValue");
m.body()._return(JExpr.invoke("valueOf").arg(m.param(String.class, "v")));
}
}
private FieldOutline generateFieldDecl(ClassOutlineImpl cc, CPropertyInfo prop) {
FieldRenderer fr = prop.realization;
if (fr == null)
{
fr = model.options.getFieldRendererFactory().getDefault();
}
FieldOutline field = fr.generate(cc, prop);
fields.put(prop, field);
return field;
}
public final void generateAdapterIfNecessary(CPropertyInfo prop, JAnnotatable field) {
CAdapter adapter = prop.getAdapter();
if (adapter != null) {
if (adapter.getAdapterIfKnown() == SwaRefAdapterMarker.class) {
field.annotate(XmlAttachmentRef.class);
} else {
XmlJavaTypeAdapterWriter xjtw = field.annotate2(XmlJavaTypeAdapterWriter.class);
xjtw.value(adapter.adapterType.toType(this, EXPOSED));
}
}
switch (prop.id()) {
case ID:
field.annotate(XmlID.class);
break;
case IDREF:
field.annotate(XmlIDREF.class);
break;
}
if (prop.getExpectedMimeType() != null) {
field.annotate2(XmlMimeTypeWriter.class).value(prop.getExpectedMimeType().toString());
}
}
public final JClass addRuntime(Class clazz) {
JClass g = generatedRuntime.get(clazz);
if (g == null) {
JPackage implPkg = getUsedPackages(Aspect.IMPLEMENTATION)[0].subPackage("runtime");
g = generateStaticClass(clazz, implPkg);
generatedRuntime.put(clazz, g);
}
return g;
}
public JClass generateStaticClass(Class src, JPackage out) {
JStaticJavaFile sjf = new JStaticJavaFile(out, getShortName(src), src, null);
out.addResourceFile(sjf);
return sjf.getJClass();
}
private String getShortName(Class src) {
String name = src.getName();
return name.substring(name.lastIndexOf('.') + 1);
}
}