/**
*
*/
package com.sun.tools.xjc.addon;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttachmentRef;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.XmlInlineBinaryData;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JFormatter;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.model.CPluginCustomization;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
/**
* XJC plugin that forces XJC to generate classes that use PROPERTY access (intead of the default FIELD access). This
* means that setters are triggered during unmarshalling and getters are triggered during marshalling.
* <p>
* Usage in schema:<br>
* Add <code><http://www.mff.cuni.cz/~peckam/java/origamist/jaxb/plugins:property-access></code> either in global or
* class' annotiation's appinfo.
*
* @author Martin Pecka
*/
public class PropertyAccessPlugin extends Plugin
{
public static final String NAMESPACE = "http://www.mff.cuni.cz/~peckam/java/origamist/jaxb/plugins";
@Override
public String getOptionName()
{
return "Xproperty-access";
}
@Override
public String getUsage()
{
return " -Xproperty-access: use PROPERTY accessors in generated classes";
}
@Override
public List<String> getCustomizationURIs()
{
return Collections.singletonList(NAMESPACE);
}
@Override
public boolean isCustomizationTagName(String nsUri, String localName)
{
return nsUri.equals(NAMESPACE) && localName.equals("property-access");
}
@Override
public boolean run(Outline model, Options opt, ErrorHandler errorHandler) throws SAXException
{
CPluginCustomization pluginCust = model.getModel().getCustomizations().find(NAMESPACE, "property-access");
boolean globalSet = false;
if (pluginCust != null) {
pluginCust.markAsAcknowledged();
globalSet = true;
}
for (ClassOutline co : model.getClasses()) {
CPluginCustomization classCust = co.target.getCustomizations().find(NAMESPACE, "property-access");
boolean classSet = false;
if (classCust != null) {
classCust.markAsAcknowledged();
classSet = true;
}
if (!(globalSet || classSet))
continue;
JAnnotationUse classAnnot = removeAnnotation(co.implClass, JDefinedClass.class, XmlAccessorType.class);
if (classAnnot == null)
continue;
co.implClass.annotate(XmlAccessorType.class).param("value", XmlAccessType.PROPERTY);
for (FieldOutline fo : co.getDeclaredFields()) {
JFieldVar field = co.implClass.fields().get(fo.getPropertyInfo().getName(false));
if (field == null)
continue;
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);
JMethod method = null;
Collection<JMethod> methods = co.implClass.methods();
for (Iterator<JMethod> it = methods.iterator(); it.hasNext();) {
JMethod meth = it.next();
if (meth.name().equals(getterName)) {
method = meth;
break;
}
}
// we could test for setter in the previous loop, but we'll prefer to annotate the getter if it exists
// (anyways, it should exist)
if (method == null) {
for (Iterator<JMethod> it = methods.iterator(); it.hasNext();) {
JMethod meth = it.next();
if (meth.name().equals(setterName)) {
method = meth;
break;
}
}
}
if (method == null)
continue;
JAnnotationUse fieldAnnot = removeAnnotation(field, JVar.class, XmlElement.class);
if (fieldAnnot == null) {
fieldAnnot = removeAnnotation(field, JVar.class, XmlAttribute.class);
if (fieldAnnot == null) {
fieldAnnot = removeAnnotation(field, JVar.class, XmlValue.class);
if (fieldAnnot == null)
continue;
}
}
List<JAnnotationUse> fieldAnnots = new LinkedList<JAnnotationUse>();
fieldAnnots.add(fieldAnnot);
List<Class<? extends Annotation>> annotsToMoveWith = new LinkedList<Class<? extends Annotation>>() {
/** */
private static final long serialVersionUID = 2422879147478430001L;
{
add(XmlElements.class);
add(XmlID.class);
add(XmlIDREF.class);
add(XmlList.class);
add(XmlSchemaType.class);
add(XmlValue.class);
add(XmlAttachmentRef.class);
add(XmlMimeType.class);
add(XmlInlineBinaryData.class);
add(XmlElementWrapper.class);
add(XmlJavaTypeAdapter.class);
}
};
for (Class<? extends Annotation> annotClass : annotsToMoveWith) {
JAnnotationUse annot = removeAnnotation(field, JVar.class, annotClass);
if (annot != null)
fieldAnnots.add(annot);
}
List<JAnnotationUse> methodAnnots = getAnnotations(method, JMethod.class);
if (methodAnnots == null) {
// initialize the inner annotations array
method.annotate(XmlElement.class);
removeAnnotation(method, JMethod.class, XmlElement.class);
methodAnnots = getAnnotations(method, JMethod.class);
}
if (methodAnnots == null) {
errorHandler.warning(new SAXParseException("Couldn't create the list of annotations for method "
+ method.name(), null));
continue;
}
methodAnnots.addAll(fieldAnnots);
}
}
return true;
}
/**
* 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;
}
protected <T> JAnnotationUse removeAnnotation(T object, Class<? super T> annotationsContainingClass,
Class<? extends Annotation> annotation)
{
List<JAnnotationUse> annots = getAnnotations(object, annotationsContainingClass);
if (annots != null) {
for (Iterator<JAnnotationUse> it = annots.iterator(); it.hasNext();) {
JAnnotationUse au = it.next();
StringWriter w = new StringWriter();
au.generate(new JFormatter(w));
if (w.toString().startsWith("@" + annotation.getName())) {
it.remove();
return au;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
protected <T> List<JAnnotationUse> getAnnotations(T object, Class<? super T> annotationsContainingClass)
{
return (List<JAnnotationUse>) getValueWithReflection("annotations", object, annotationsContainingClass);
}
}