// ///////////////////////////////////////////////////////////////////////////
//
// $RCSfile: $
//
// Project JaxbPluginJavaForKmlApi
//
// Author Flori (f.bachmann@micromata.de)
// Created 20.03.2009
// Copyright Micromata 20.03.2009
//
// $Id: $
// $Revision: $
// $Date: $
//
// ///////////////////////////////////////////////////////////////////////////
package org.jvnet.jaxb2_commons.javaforkmlapi.fluent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import org.jvnet.jaxb2_commons.javaforkmlapi.ClazzPool;
import org.jvnet.jaxb2_commons.javaforkmlapi.Util;
import org.jvnet.jaxb2_commons.javaforkmlapi.XJCJavaForKmlApiPlugin;
import org.jvnet.jaxb2_commons.javaforkmlapi.command.Command;
import org.xml.sax.ErrorHandler;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.generator.bean.ClassOutlineImpl;
import com.sun.tools.xjc.model.CClassInfo;
import com.sun.tools.xjc.model.CPropertyInfo;
import com.sun.tools.xjc.outline.Aspect;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.reader.TypeUtil;
public class FluentPattern extends Command {
private static final Logger LOG = Logger.getLogger(FluentPattern.class.getName());
private final JCodeModel codeModel;
private final HashMap<String, ClassOutlineImpl> classList;
private final HashMap<String, ArrayList<CClassInfo>> subclasses;
private JDefinedClass annotateObvicious = null;
private JDefinedClass classCoordinates = null;
private final JDefinedClass classIcon;
private final JDefinedClass classLink;
public FluentPattern(final Outline outline, final Options opts, final ErrorHandler errorHandler, final ClazzPool pool) {
super(outline, opts, errorHandler, pool);
annotateObvicious = pool.getClassObviousAnnotation();
LOG.info(XJCJavaForKmlApiPlugin.PLUGINNAME + " generate Fluent API + " + annotateObvicious.name());
classCoordinates = pool.getClassCoordinate();
classList = Util.getClassList(outline);
codeModel = outline.getCodeModel();
subclasses = Util.findSubClasses(outline);
classIcon = pool.getClassIcon();
classLink = pool.getClassLink();
}
@Override
public void execute() {
LOG.info(XJCJavaForKmlApiPlugin.PLUGINNAME + " generate Fluent API");
for (final ClassOutline classOutline : outline.getClasses()) {
final ClassOutlineImpl cc = (ClassOutlineImpl) classOutline;
final JDefinedClass implClass = classOutline.implClass;
for (final FieldOutline fieldOutline : classOutline.getDeclaredFields()) {
JType type = TypeUtil.getCommonBaseType(codeModel, Util.listPossibleTypes(cc, fieldOutline.getPropertyInfo()));
if (((type.name().equals("BasicLink") || ((type.name().equals("Link")) && Util.upperFirst(fieldOutline.getPropertyInfo().getName(false)).equals("Icon"))))) {
/*
* special case for Icon. IconStyle uses protected BasicLink icon.
* subclasses.get(currentFieldName) will return, that
* possible subclasses for BasicLink are Icon and Link,
* hence createAndSetLink and createAndSetIcon methods
* are created. This isn't desired as IconStyle should
* define protected Icon icon.
*
* If it is possible to change the type of a variable
* at the CClassInfo-Level, this if block would be
* obsolete.
*/
type = classIcon.unboxify();
}
final String currentFieldName = Util.eliminateTypeSuffix(type.name());
// if (fieldOutline.getPropertyInfo().getName(false).equals("coordinates")) {
// LOG.info("+1 "+ cc.implRef.name() + " " + currentFieldName + " " + fieldOutline.getPropertyInfo().getName(false));
// }
if (currentFieldName.equals("AbstractObject")) {
// LOG.info("skip abstract object");
continue;
}
if (subclasses.containsKey(currentFieldName) == false) {
// LOG.info("- " + currentFieldName + " > type.name(): " + type.name());
continue;
}
if (fieldOutline.getPropertyInfo().getName(false).contains("Deprecated")) {
// LOG.info("skip deprecated");
continue;
}
final ArrayList<CClassInfo> subclasseslist = subclasses.get(currentFieldName);
// LOG.info("+ " + currentFieldName + " " + fieldOutline.getPropertyInfo().getName(false));
// if (type.unboxify().name().equals("Link") && fieldOutline.getPropertyInfo().getName(false).equals("icon")) {
// type = classIcon.unboxify();
// }
// if (subclasseslist.size() > 0 && !(type.name().equals("Icon") || type.name().equals("Link") || currentFieldName.equals("Icon") || currentFieldName.equals("Link"))) {
if (subclasseslist.size() > 0) { // && !(type.name().equals("Icon") || type.name().equals("Link") || currentFieldName.equals("Icon") || currentFieldName.equals("Link"))) {
for (final CClassInfo cClassInfo : subclasseslist) {
// LOG.info("1<<>><<>><<>>fn 1:"+ cc.implClass.name() + " 2:" + cClassInfo.toType(outline, Aspect.EXPOSED).name() + " 3:" + cClassInfo.shortName + " 4:"+type.name() );
generateCreateAndSetOrAddMethod(outline, cc, implClass, fieldOutline, cClassInfo.toType(outline, Aspect.EXPOSED),
cClassInfo.shortName);
}
continue;
}
// use variable-name everywhere instead of variable-type-name (because of Vec2-name conflict)
// LOG.info("2<<>><<>><<>>fn 1:"+ cc.implClass.name() + " 2:" + type.name() + " 3:" + Util.upperFirst(fieldOutline.getPropertyInfo().getName(false)) + " 4:"+type.name());
generateCreateAndSetOrAddMethod(outline, cc, implClass, fieldOutline, type, Util.upperFirst(fieldOutline.getPropertyInfo().getName(false)));
}
generateSetAndAddToCollection(cc);
generateWithMethods(cc);
}
}
/*
* generates the following code:
*
* <pre> void setField(T field) { this.field = field; } </pre>
*/
private void generateSetCollection(final ClassOutlineImpl cc, final JFieldVar field, final boolean override) {
// creates the setter
final JMethod generateSet = cc.implClass.method(JMod.PUBLIC, cc.implClass.owner().VOID, "set" + Util.upperFirst(field.name()));
final JVar value = generateSet.param(JMod.FINAL, field.type(), field.name());
// set the assignment to the body: this.value = value;
if (override) {
if (annotateObvicious != null) {
generateSet.annotate(annotateObvicious);
}
generateSet.annotate(Override.class);
// super.setObjectSimpleExtensionGroup(objectSimpleExtensionGroup);
// generateSet.body().assign(JExpr._this().ref(field.name()), value);
generateSet.body().directStatement("super.set" + Util.upperFirst(field.name()) + "(" + value.name() + ");");
} else {
generateSet.javadoc().append("Sets the value of the " + field.name() + " property");
generateSet.javadoc().addParam(value);
generateSet.javadoc().append("Objects of the following type(s) are allowed in the list ");
generateSet.javadoc().append(field.type().name());
generateSet.javadoc().append(".\n<p>Note:\n<p>");
generateSet.javadoc().append("This method does not make use of the fluent pattern.");
generateSet.javadoc()
.append("If you would like to make it fluent, use {@link #with" + Util.upperFirst(field.name()) + "} instead.\n");
generateSet.body().assign(JExpr._this().ref(field.name()), value);
}
// LOG.info("s> " + implClass.name() + " >> public void set" + JaxbUtil.upperFirst(field.name()) + "(" + field.type().name() +
// ") created [Override: " + override + "].");
}
/**
* generates the following code:
*
* <pre>
* boolean addCollection(T field) {
* if (collection == null) {
* collection = new ArrayList<T>();
* }
* return this.collection.add(field);
* }
* // changed into: return this.getCollection().add(abstractFeatureGroup);
* </pre>
* @param type2
*/
private void generateAddToCollection(final ClassOutlineImpl cc, final JFieldVar field, final boolean override) {
final CPropertyInfo propertyInfo = Util.searchPropertyInfo(cc, field.name());
if (propertyInfo == null) {
return;
}
if (field.name().equals("coordinates") && !override) {
LOG.info("its a coordinate");
coordinateAddtoCase(cc, field, propertyInfo);
return;
}
commonAddtoCase(cc, field, override, propertyInfo);
}
private void coordinateAddtoCase(final ClassOutlineImpl cc, final JFieldVar field, final CPropertyInfo propertyInfo) {
final Collection<JFieldVar> coordinateCreateMethods = new ArrayList<JFieldVar>();
final JFieldVar longitude = classCoordinates.fields().get("longitude");
final JFieldVar latitude = classCoordinates.fields().get("latitude");
coordinateCreateMethods.add(longitude);
coordinateCreateMethods.add(latitude);
createArgFactoryMethod(cc, coordinateCreateMethods);
final JFieldVar altitude = classCoordinates.fields().get("altitude");
coordinateCreateMethods.add(altitude);
createArgFactoryMethod(cc, coordinateCreateMethods);
coordinateCreateMethods.clear();
// create public Point addToCoordinates(final String coordinates) {...}
final JMethod generateAdd = cc.implClass.method(JMod.PUBLIC, cc.implClass, "addToCoordinates");
generateAdd.javadoc().append("add a value to the coordinates property collection");
final JInvocation returntype = JExpr._new(classCoordinates);
final JVar arg = generateAdd.param(JMod.FINAL, String.class, field.name());
generateAdd.javadoc().addParam(arg).append("required parameter");
returntype.arg(arg);
generateAdd.javadoc().addReturn().append("<tt>true</tt> (as general contract of <tt>Collection.add</tt>). ");
generateAdd.body().add(JExpr._this().invoke("getCoordinates").invoke("add").arg(returntype));
generateAdd.body()._return(JExpr._this());
}
private void createArgFactoryMethod(final ClassOutlineImpl cc, final Collection<JFieldVar> coordinateCreateMethods) {
final JMethod generateAdd = cc.implClass.method(JMod.PUBLIC, cc.implClass, "addToCoordinates");
generateAdd.javadoc().append("add a value to the coordinates property collection");
final JInvocation returntype = JExpr._new(classCoordinates);
for (final JFieldVar field : coordinateCreateMethods) {
final JVar arg = generateAdd.param(JMod.FINAL, field.type(), field.name());
generateAdd.javadoc().addParam(arg).append("required parameter");
returntype.arg(arg);
}
generateAdd.javadoc().addReturn().append("<tt>true</tt> (as general contract of <tt>Collection.add</tt>). ");
generateAdd.body().add(JExpr._this().invoke("getCoordinates").invoke("add").arg(returntype));
generateAdd.body()._return(JExpr._this());
}
private void commonAddtoCase(final ClassOutlineImpl cc, final JFieldVar field, final boolean override, final CPropertyInfo propertyInfo) {
// find the common type
final JType type = TypeUtil.getCommonBaseType(codeModel, Util.listPossibleTypes(cc, propertyInfo));
// creates the add method
final JMethod generateAdd = cc.implClass.method(JMod.PUBLIC, cc.implClass, "addTo" + Util.upperFirst(field.name()));
final JVar value = generateAdd.param(JMod.FINAL, type, field.name());
if (override) {
if (annotateObvicious != null) {
generateAdd.annotate(annotateObvicious);
}
generateAdd.annotate(Override.class);
// if (field.name().equals("coordinates")) {
// LOG.info("####################### coordinates found : create new #######################");
// generateAdd.body().add(JExpr._super().invoke("get" +
// Util.upperFirst(field.name())).invoke("add").arg(JExpr._new(annotateCoordinates).arg(value)));
// } else {
generateAdd.body().add(JExpr._super().invoke("get" + Util.upperFirst(field.name())).invoke("add").arg(value));
// LOG.info("####################### ####################### " + field.name());
// }
// generateAdd.body().directStatement("super.get" + Util.upperFirst(field.name()) + "().add(" + value.name() + ");");
} else {
generateAdd.javadoc().append("add a value to the " + field.name() + " property collection");
generateAdd.javadoc().addParam(value).append("Objects of the following type are allowed in the list: ").append(
Util.listPossibleTypes(cc, propertyInfo));
generateAdd.javadoc().addReturn().append("<tt>true</tt> (as general contract of <tt>Collection.add</tt>). ");
generateAdd.body().directStatement("this.get" + Util.upperFirst(field.name()) + "().add(" + value.name() + ");");
}
generateAdd.body()._return(JExpr._this());
// LOG.info("a> " + cc.implClass.name() + " >> public " + cc.implClass.name() + " addTo" + Util.upperFirst(field.name()) + "(" +
// type.name() + ") created [Override: " + override + "].");
}
/*
* generates the following code:
*
* <pre> void setField(T field) { this.field = field; } </pre>
*/
private void generateSetAndAddToCollection(final ClassOutlineImpl cc) {
final Collection<JFieldVar> optionalFluentFields = Util.getAllFieldsFields(cc, true);
if (optionalFluentFields.size() == 0) {
return;
}
// LOG.info(cc.implRef.name() + " contains fields: " + optionalFluentFields.size());
for (final JFieldVar field : optionalFluentFields) {
generateSetCollection(cc, field, false);
generateAddToCollection(cc, field, false);
}
// check for methods in superclass
final Collection<JFieldVar> superclassFields = Util.getSuperclassAllFields(cc, true);
if (superclassFields.size() == 0) {
return;
}
// LOG.info(cc.implRef.name() + " contains super fields: " + superclassFields.size());
for (final JFieldVar field : superclassFields) {
generateSetCollection(cc, field, true);
generateAddToCollection(cc, field, true);
}
}
/*
* generates the following code:
*
* <pre> void setField(T field) { this.field = field; } </pre>
*/
private void generateWith(final ClassOutlineImpl cc, final JFieldVar field, final boolean override) {
// creates the setter
final JMethod generateWith = cc.implClass.method(JMod.PUBLIC, cc.implClass, "with" + Util.upperFirst(field.name()));
final JVar value = generateWith.param(JMod.FINAL, Util.removeJAXBElement(cm, field.type()), field.name());
// set the assignment to the body: this.value = value;
if (override) {
if (annotateObvicious != null) {
generateWith.annotate(annotateObvicious);
}
generateWith.annotate(Override.class);
// super.setObjectSimpleExtensionGroup(objectSimpleExtensionGroup);
// generateSet.body().assign(JExpr._this().ref(field.name()), value);
generateWith.body().directStatement("super.with" + Util.upperFirst(field.name()) + "(" + field.name() + ");");
} else {
generateWith.javadoc().add("fluent setter");
generateWith.javadoc().addParam(value).append("required parameter");
generateWith.javadoc().add("\n@see #set"+Util.upperFirst(field.name())+"("+value.type().name()+")");
generateWith.body().directStatement("this.set" + Util.upperFirst(field.name()) + "(" + field.name() + ");");
}
// generateSet.body().directStatement("this."+field.name() + " = " + field.name()+";");
// generate the javadoc
generateWith.body()._return(JExpr._this());
LOG.info("w> " + cc.implClass.name() + " >> public " + cc.implClass.name() + " with" + Util.upperFirst(field.name()) + "("
+field.type().name() + ") created [Override: " + override + "].");
}
private void generateWithMethods(final ClassOutlineImpl cc) {
final Collection<JFieldVar> optionalFluentFields = Util.getFields(cc, false, false);
if (optionalFluentFields.size() == 0) {
return;
}
// LOG.info(cc.implRef.name() + " contains fields: " + optionalFluentFields.size());
for (final JFieldVar field : optionalFluentFields) {
generateWith(cc, field, false);
}
// check for methods in superclass
final Collection<JFieldVar> superclassFields = Util.getSuperclassFields(cm, cc, false, false);
if (superclassFields.size() == 0) {
return;
}
// LOG.info(cc.implRef.name() + " contains super fields: " + superclassFields.size());
for (final JFieldVar field : superclassFields) {
generateWith(cc, field, true);
}
}
private void generateCreateAndSetOrAddMethod(final Outline outline, final ClassOutlineImpl cc, final JDefinedClass implClass,
final FieldOutline fieldOutline, final JType cClassInfo, final String shortName) {
final StringBuffer debugOut = new StringBuffer();
final String localName = "newValue";
final CPropertyInfo property = fieldOutline.getPropertyInfo();
final StringBuffer methodName = new StringBuffer();
final ArrayList<String> javadoc = new ArrayList<String>();
methodName.append("createAnd");
JInvocation methodInvoke = null;
if (property.isCollection()) {
methodName.append("Add");
methodInvoke = JExpr._this().invoke("get" + property.getName(true)).invoke("add").arg(JExpr.ref(localName));
javadoc.add("and adds it to " + property.getName(false) + ".");
javadoc.add("\n");
javadoc.add("This method is a short version for:\n");
javadoc.add("<code>\n");
javadoc.add("" + cClassInfo.name() + " " + Util.lowerFirst(cClassInfo.name()) + " = new " + cClassInfo.name() + "();\n");
javadoc.add("this.get" + property.getName(true) + "().add(" + Util.lowerFirst(cClassInfo.name()) + ");");
javadoc.add("</code>\n");
} else {
methodName.append("Set");
methodInvoke = JExpr._this().invoke("set" + property.getName(true)).arg(JExpr.ref(localName));
javadoc.add("and set it to " + property.getName(false) + ".\n");
javadoc.add("\n");
javadoc.add("This method is a short version for:\n");
javadoc.add("<code>\n");
javadoc.add("" + cClassInfo.name() + " " + Util.lowerFirst(cClassInfo.name()) + " = new " + cClassInfo.name() + "();\n");
javadoc.add("this.set" + property.getName(true) + "(" + Util.lowerFirst(cClassInfo.name()) + ");");
javadoc.add("</code>\n");
}
// cClassInfo
final ClassOutlineImpl asClass = classList.get(cClassInfo.fullName());
final Collection<JFieldVar> relevantFields = Util.getConstructorRequiredFields(asClass);
final Map<String, FieldOutline> fieldOutlineasMap = Util.getRequiredFieldsAsMap(cc);
final JMethod m = implClass.method(JMod.PUBLIC, cClassInfo, methodName.toString() + shortName);
final JInvocation args = JExpr._new(cClassInfo);
for (final JFieldVar field : relevantFields) {
// FieldOutline fo = fieldOutlineasMap.get(field.name());
// if (fo == null) {
// continue;
// }
// if (fo.getPropertyInfo().isCollection()) {
// LOG.info("!!!!! " + cc.implClass.name() + " is collection " + methodName );
// continue;
// }
final JVar arg = m.param(JMod.FINAL, field.type(), field.name());
args.arg(JExpr.ref(field.name()));
m.javadoc().addParam(arg).append("required parameter");
// m.body().assign(JExpr.refthis(field.name()), arg);
}
m.body().decl(cClassInfo, localName, args);
m.body().add(methodInvoke);
m.body()._return(JExpr.ref(localName));
m.javadoc().append("Creates a new instance of ");
m.javadoc().append(cClassInfo);
m.javadoc().append(javadoc);
debugOut.append("m> " + cc.implRef.name() + " :: public " + cClassInfo.name() + " " + methodName.toString() + shortName + "(");
for (final JFieldVar field : relevantFields) {
debugOut.append(field.type().name() + ", ");
}
if (relevantFields.size() > 0) {
debugOut.delete(debugOut.length() - 2, debugOut.length());
}
debugOut.append(") created.");
LOG.info(debugOut.toString());
}
}