/**
*
*/
package com.sun.tools.xjc.addon;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JStatement;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
/**
* XJC plugin (to be accurate, bugfix). If you define a type with implClass and use it in the schema in such way, that
* XJC generates a list of these elements, then XJC originally uses the generated class and not the implClass as the
* type of elements in the list. This plugin fixes it. Also this tells XJC to use the user-defined implClass in
* generated getters and setters.
*
* @author Martin Pecka
*/
public class PatchForImplClassAndList extends Plugin
{
@Override
public String getOptionName()
{
return "Xpatch-implClass-list";
}
@Override
public String getUsage()
{
return " -Xpatch-implClass-list: generate lists using the defined implClass of its items";
}
@Override
public List<String> getCustomizationURIs()
{
return Collections.singletonList("http://www.mff.cuni.cz/~peckam/java/origamist/jaxb/plugins/patch");
}
@Override
public boolean run(Outline model, Options opt, ErrorHandler errorHandler) throws SAXException
{
// collect classes that have a user-defined implementation class
Hashtable<String, String> elementsWithImplClassSet = new Hashtable<String, String>();
for (ClassOutline co : model.getClasses()) {
if (co.target.getUserSpecifiedImplClass() != null)
elementsWithImplClassSet.put(co.implClass.fullName(), co.target.getUserSpecifiedImplClass());
}
for (ClassOutline co : model.getClasses()) {
for (FieldOutline fo : co.getDeclaredFields()) {
JClass clazz = fo.getRawType().boxify();
String implClassName = elementsWithImplClassSet.get(clazz.fullName()); // name of the user-defined class
JClass implClass;
JClass fieldClass;
boolean isList;
if (implClassName != null) {
// not a field with implClass resulting in a List
implClass = model.getCodeModel().ref(implClassName);
fieldClass = implClass;
isList = false;
} else if ("java.util.List".equals(fo.getRawType().erasure().fullName())) {
// a field with implClass resulting in a list
List<JClass> parameters = ((JClass) fo.getRawType()).getTypeParameters();
if (parameters == null)
continue;
// we only expect List<E>
if (parameters.size() > 1 || parameters.size() == 0)
continue;
JClass c = parameters.get(0);
implClassName = elementsWithImplClassSet.get(c.fullName()); // name of the user-defined class
if (implClassName == null)
continue;
implClass = model.getCodeModel().ref(implClassName);
// a java.util.List with the corect type argument
fieldClass = ((JClass) fo.getRawType().erasure()).narrow(implClass);
isList = true;
} else {
// this is nor a field with implClass nor a list field with implClass
continue;
}
JFieldVar field = co.implClass.fields().get(fo.getPropertyInfo().getName(false));
String getterName = "get" + field.name().substring(0, 1).toUpperCase() + field.name().substring(1);
String setterName = "set" + field.name().substring(0, 1).toUpperCase() + field.name().substring(1);
JClass listImplClass = null;
JClass newListImplClass = null;
if (isList) {
listImplClass = (JClass) getValueWithReflection("coreList", fo, fo.getClass());
assert listImplClass != null;
newListImplClass = listImplClass.erasure().narrow(implClass);
// remove the old invalid field
co.implClass.removeField(field);
// replace the old field with the correct one
JFieldVar newField = co.implClass.field(field.mods().getValue(), fieldClass, field.name(),
JExpr._new(newListImplClass));
copyFieldValueWithReflection("annotations", field, newField, field.getClass().getSuperclass());
copyFieldValueWithReflection("jdoc", field, newField, field.getClass());
field = newField;
}
// update getter for the list (no setter is created) getter/setter arguments types
for (JMethod m : co.implClass.methods()) {
if (getterName.equals(m.name())) {
// change the return type of the method
m.type(fieldClass);
if (isList) {
assert newListImplClass != null;
JBlock body = m.body();
JBlock newBody = new JBlock(true, true);
for (Object o : body.getContents()) {
if (o instanceof JConditional) {
JConditional cond = newBody._if(field.eq(JExpr._null()));
JBlock thenBlock = cond._then();
thenBlock.assign(field, JExpr._new(newListImplClass));
} else if (o instanceof JStatement) {
newBody.add((JStatement) o);
}
}
setValueWithReflection("body", m, m.getClass(), newBody);
}
// change the javadoc to reflect the new reality
if (m.javadoc().size() >= 3) {
m.javadoc().remove(m.javadoc().size() - 1);
m.javadoc().remove(m.javadoc().size() - 1);
m.javadoc().remove(m.javadoc().size() - 1);
if (implClass != fieldClass) {
m.javadoc().append("<p>Objects of the following type(s) are allowed in the list: ");
} else {
m.javadoc().append("Possible object is: ");
}
m.javadoc().append(implClass);
}
} else if (setterName.equals(m.name())) {
setValueWithReflection("params", m, m.getClass(), new ArrayList<JVar>());
m.param(fieldClass, "value");
// change the javadoc to reflect the new reality
if (m.javadoc().size() >= 3) {
m.javadoc().remove(m.javadoc().size() - 1);
m.javadoc().remove(m.javadoc().size() - 1);
m.javadoc().remove(m.javadoc().size() - 1);
m.javadoc().append("Allowed object is: ");
m.javadoc().append(fieldClass);
}
}
}
}
}
// BEWARE: uncommenting the next lines would be nice (object factories would return elements of implClass type),
// but JAXB cannot handle that and unmarshalling ens up with a ClassCastException.
// Set<String> oldClasses = elementsWithImplClassSet.keySet();
// for (PackageOutline pack : model.getAllPackageContexts()) {
// JDefinedClass factory = pack.objectFactory();
// for (JMethod m : factory.methods()) {
// if (oldClasses.contains(m.type().fullName())) {
// m.type(model.getCodeModel().ref(elementsWithImplClassSet.get(m.type().fullName())));
// }
// }
// }
return true;
}
/**
* Uses reflection to copy value of the field <code>field</code> from <code>oldObj</code> to <code>newObj</code>.
*
* @param field The name of the field to copy.
* @param oldObj The source object.
* @param newObj The target object.
* @param clazz The class containing the field (exactly that class, not a subclass or so!).
*/
protected void copyFieldValueWithReflection(String field, Object oldObj, Object newObj, Class<?> clazz)
{
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (!field.equals(f.getName()))
continue;
f.setAccessible(true);
try {
f.set(newObj, f.get(oldObj));
} catch (IllegalArgumentException e) {
System.err.println(e);
} catch (IllegalAccessException e) {
System.err.println(e);
}
}
}
/**
* Uses reflection to set the value of the field <code>field</code> of <code>obj</code> to <code>value</code>.
*
* @param field The name of the field to copy.
* @param obj The object we set a field of.
* @param clazz The class containing the field (exactly that class, not a subclass or so!).
* @param value The value to set.
*/
protected void setValueWithReflection(String field, Object obj, Class<?> clazz, Object value)
{
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (!field.equals(f.getName()))
continue;
f.setAccessible(true);
try {
f.set(obj, value);
} catch (IllegalArgumentException e) {
System.err.println(e);
} catch (IllegalAccessException e) {
System.err.println(e);
}
}
}
/**
* Uses reflection to get the value of the field <code>field</code> of <code>obj</code>.
*
* @param field The name of the field to get.
* @param obj The object we get a field of.
* @param clazz The class containing the field (exactly that class, not a subclass or so!).
*/
protected Object getValueWithReflection(String field, Object obj, Class<?> clazz)
{
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (!field.equals(f.getName()))
continue;
f.setAccessible(true);
try {
return f.get(obj);
} catch (IllegalArgumentException e) {
System.err.println(e);
} catch (IllegalAccessException e) {
System.err.println(e);
}
}
return null;
}
}