package com.google.code.joto.value2java;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.google.code.joto.JotoConfig;
import com.google.code.joto.ast.beanstmt.BeanAST;
import com.google.code.joto.ast.beanstmt.BeanAST.AssignExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.BeanExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.BeanStmt;
import com.google.code.joto.ast.beanstmt.BeanAST.ExprStmt;
import com.google.code.joto.ast.beanstmt.BeanAST.IndexedArrayExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.LiteralExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.MethodApplyExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.NewArrayExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.NewObjectExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.SimpleNameExpr;
import com.google.code.joto.ast.beanstmt.BeanAST.VarDeclStmt;
import com.google.code.joto.ast.valueholder.ValueHolderAST.AbstractObjectValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.ArrayEltRefValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.CollectionEltRefValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.CollectionValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.FieldValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.ImmutableObjectValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.MapEntryKeyRefValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.MapEntryValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.MapEntryValueRefValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.MapValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.ObjectValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.PrimitiveArrayEltValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.PrimitiveArrayValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.PrimitiveFieldValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.RefArrayValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderAST.RefFieldValueHolder;
import com.google.code.joto.ast.valueholder.ValueHolderVisitor2;
import com.google.code.joto.reflect.ClassDictionaryJotoInfo;
import com.google.code.joto.reflect.ClassJotoInfo;
import com.google.code.joto.reflect.ConstructorJotoInfo;
import com.google.code.joto.reflect.ParamToFieldInfo;
import com.google.code.joto.util.NameGenerator;
import com.google.code.joto.value2java.impl.ObjectStmtInfo;
/**
*
*/
public class VHToStmt implements ValueHolderVisitor2<BeanAST,ObjectStmtInfo> {
private Map<AbstractObjectValueHolder,ObjectStmtInfo> objStmtInfoMap =
new IdentityHashMap<AbstractObjectValueHolder,ObjectStmtInfo>();
private NameGenerator nameGenerator = new NameGenerator();
private VarDeclStmt logVarDeclStmt;
private JotoConfig config;
// may move in JotoContext??? (but not JotoConfig since nothing is supposed to be persistent / serializable)
// contains mainly static cached data, computed from Class
private ClassDictionaryJotoInfo classDicInfo = ClassDictionaryJotoInfo.getDefaultInstance();
// -------------------------------------------------------------------------
public VHToStmt(JotoConfig config) {
this.config = config;
}
// ------------------------------------------------------------------------
public Map<AbstractObjectValueHolder, ObjectStmtInfo> getResultObjInitInfoMap() {
return objStmtInfoMap;
}
public JotoConfig getConfig() {
return config;
}
public void setConfig(JotoConfig config) {
this.config = config;
}
// public VHToStmtConverterLookup getVhToStmtConverterLookup() {
// return vhToStmtConverterLookup;
// }
//
// public void setVhToStmtConverterLookup(VHToStmtConverterLookup p) {
// this.vhToStmtConverterLookup = p;
// }
public void visitRootObject(AbstractObjectValueHolder objVH, String name) {
objToInitInfo(objVH, name);
}
public ClassJotoInfo getClassInfo(Class<?> objClass) {
return classDicInfo.getClassInfo(objClass);
}
// implements ValueHolderVisitor2
// -------------------------------------------------------------------------
@Override
public BeanAST caseObject(ObjectValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr initExpr;
Class<?> objClass = p.getObjClass();
ObjectVHToStmtConverter converter = config.lookupConverter(objClass);
if (converter != null && converter.canConvert(objClass)) {
converter.convert(this, objInfo.getObjectVH(), objInfo); // ??? TOCHECK
initExpr = objInfo.getVarDeclStmt().getInitExpr(); // ??
} else {
Map<Field,FieldValueHolder> fieldsToSet = new HashMap<Field,FieldValueHolder>(p.getFieldsValuesMap());
// choose one ctor, public, with @ConstructorProperties..
ClassJotoInfo classInfo = getClassInfo(objClass);
// List<ConstructorJotoInfo> ctorInfos = classInfo.getConstructorInfos();
ConstructorJotoInfo ctorInfo = classInfo.choosePublicCtorWithInfo();
List<BeanExpr> ctorExprs = new ArrayList<BeanExpr>();
if (ctorInfo != null) {
List<ParamToFieldInfo> ctorParamToFieldInfos = ctorInfo.getParamToFieldInfos();
for(ParamToFieldInfo elt : ctorParamToFieldInfos) {
Field field = elt.getTargetAssignedField();
FieldValueHolder vh = fieldsToSet.remove(field);
// convert field -> value -> BeanExpr (not BeanStmt using visitor)
BeanExpr valueExpr;
if (vh instanceof PrimitiveFieldValueHolder) {
PrimitiveFieldValueHolder vh2 = (PrimitiveFieldValueHolder) vh;
Object value = vh2.getValue();
valueExpr = new LiteralExpr(value);
} else { // (vh instanceof RefObjectFieldValueNode)
RefFieldValueHolder vh2 = (RefFieldValueHolder) vh;
AbstractObjectValueHolder fieldVH = vh2.getTo();
String prefixName = field.getName();
valueExpr = objToLhsExpr(fieldVH, prefixName);
}
ctorExprs.add(valueExpr);
}
}
// use field values expr as ctor parameters
initExpr = doNewDefaultObjInstanceExpr(objInfo, p, ctorExprs);
doSetObjInitExpr(objInfo, initExpr);
// TODO TEMPORARY HACK
String className = objClass.getName();
final boolean recurseInObj = !(className.startsWith("com.lyxor.model.businessobject."));
if (!recurseInObj) {
return initExpr;
}
// convert remaining field values to setter stmt
// TODO TEMPORARY HACK: sort by field name
for(Map.Entry<Field,FieldValueHolder> e : fieldsToSet.entrySet()) {
FieldValueHolder fieldVH = e.getValue();
BeanStmt fieldStmt = (BeanStmt) fieldVH.visit(this, objInfo);
objInfo.addInitStmt(fieldStmt);
}
}
return initExpr;
}
@Override
public BeanAST casePrimitiveField(PrimitiveFieldValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr lhsExpr = objToLhsExpr(objInfo);
BeanExpr argExpr = new LiteralExpr(p.getValue());
return doCaseFieldSetterExprStmt(p.getField(), lhsExpr, argExpr);
}
@Override
public BeanAST caseRefField(RefFieldValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr lhsExpr = objToLhsExpr(objInfo);
String namePrefix = p.getField().getName();
AbstractObjectValueHolder refVH = p.getTo();
// ** recurse **
ObjectStmtInfo refObjInfo = objToInitInfo(refVH, namePrefix);
BeanExpr argExpr = objToLhsExpr(refObjInfo);
return doCaseFieldSetterExprStmt(p.getField(), lhsExpr, argExpr);
}
private BeanStmt doCaseFieldSetterExprStmt(Field field,
BeanExpr lhsExpr, BeanExpr argExpr) {
// find corresponding setter for initializing "obj.setField(value)"
PropertyDescriptor prop = findPropertyDesc(field);
String setterName;
if (prop == null) {
return newLogWarnStmt("prop not found for field " + field);
} else if (prop.getWriteMethod() == null) {
return newLogWarnStmt("writeMethod not found for prop " + field);
} else if (prop.getWriteMethod().isAccessible()) {
return newLogWarnStmt("writeMethod not accessible for prop " + field);
} else {
setterName = prop.getWriteMethod().getName();
}
BeanExpr expr = new MethodApplyExpr(lhsExpr, setterName, argExpr);
return new ExprStmt(expr);
}
@Override
public BeanAST caseImmutableObjectValue(ImmutableObjectValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr initExpr;
if (p.getObjClass().equals(String.class)) {
initExpr = new LiteralExpr(p.getValue());
} else {
BeanExpr ctorArg = new LiteralExpr(p.getValue());
initExpr = doNewDefaultObjInstanceExpr(objInfo, p, ctorArg);
}
doSetObjInitExpr(objInfo, initExpr);
return initExpr;
}
@Override
public BeanAST caseCollection(CollectionValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr initExpr = doNewDefaultObjInstanceExpr(objInfo, p);
doSetObjInitExpr(objInfo, initExpr);
BeanExpr lsExpr = objToLhsExpr(objInfo);
String addMethodName = "add";
Collection<CollectionEltRefValueHolder> eltVHs = p.getEltRefs();
String eltNamePrefix = objInfo.getVarNameWithSuffix("Elt");
int eltIndex = 0;
for(CollectionEltRefValueHolder eltVH : eltVHs) {
String indexedEltNamePrefix = eltNamePrefix + eltIndex; // ??
// *** recurse ***
BeanExpr eltExpr = (BeanExpr)
// eltVH.visit(this, objInfo); ??? use name??
objToLhsExpr(eltVH.getTo(), indexedEltNamePrefix);
MethodApplyExpr eltAddExpr = new MethodApplyExpr(lsExpr, addMethodName, eltExpr);
objInfo.addInitStmt(new ExprStmt(eltAddExpr));
eltIndex++;
}
return initExpr;
}
@Override
public BeanAST caseCollectionElt(CollectionEltRefValueHolder p, ObjectStmtInfo objInfo) {
// NOT USED, cf caseCollection()!
BeanExpr eltExpr = objToLhsExpr(p.getTo(), null);
return eltExpr;
}
@Override
public BeanAST caseMap(MapValueHolder p, ObjectStmtInfo objInfo) {
BeanExpr initExpr = doNewDefaultObjInstanceExpr(objInfo, p);
doSetObjInitExpr(objInfo, initExpr);
BeanExpr lsExpr = objToLhsExpr(objInfo);
String keyNamePrefix = objInfo.getVarNameWithSuffix("Key");
String valueNamePrefix = objInfo.getVarNameWithSuffix("Value");
String putMethodName = "put";
Collection<MapEntryValueHolder> entryVHs = p.getEntries();
for(MapEntryValueHolder entryVH : entryVHs) {
// *** recurse ***
BeanExpr keyExpr = objToLhsExpr(entryVH.getKey(), keyNamePrefix);
BeanExpr valueExpr = objToLhsExpr(entryVH.getValue(), valueNamePrefix);
MethodApplyExpr eltAddExpr = new MethodApplyExpr(lsExpr, putMethodName, keyExpr, valueExpr);
objInfo.addInitStmt(new ExprStmt(eltAddExpr));
}
return initExpr;
}
@Override
public BeanAST caseMapEntry(MapEntryValueHolder p, ObjectStmtInfo arg) {
// NOT USED, cf caseMap()!
return null;
}
@Override
public BeanAST caseMapEntryKey(MapEntryKeyRefValueHolder p, ObjectStmtInfo arg) {
// NOT USED, cf caseMap()!
return null;
}
@Override
public BeanAST caseMapEntryValue(MapEntryValueRefValueHolder p, ObjectStmtInfo arg) {
// NOT USED, cf caseMap()!
return null;
}
@Override
public BeanAST casePrimitiveArray(PrimitiveArrayValueHolder<?> p, ObjectStmtInfo objInfo) {
PrimitiveArrayEltValueHolder<?>[] arrayVH = p.getHolderArray();
int len = arrayVH.length;
Class<?> compClass = p.getObjClass().getComponentType();
BeanExpr initExpr = new NewArrayExpr(compClass, len);
doSetObjInitExpr(objInfo, initExpr);
BeanExpr lhsArrayExpr = objToLhsExpr(objInfo);
for(int i = 0; i < len; i++) {
PrimitiveArrayEltValueHolder<?> eltVH = arrayVH[i];
// *** recurse ***
BeanExpr eltExpr = new LiteralExpr(eltVH.getValue());
// TODO test if it is not necessary to set default values.... array[i] = 0; 0l; 0.0f; 0.0; false; '0' ...
boolean setToDefault = false;
if (!setToDefault) {
// stmt for "array[i] = expr"
ExprStmt assignIndexStmt = newAssignArrayIndexStmt(lhsArrayExpr, i, eltExpr);
objInfo.addInitStmt(assignIndexStmt);
}
}
return initExpr;
}
@Override
public BeanAST caseRefArray(RefArrayValueHolder p, ObjectStmtInfo objInfo) {
AbstractObjectValueHolder[] eltsVH = p.getElts();
int len = eltsVH.length;
Class<?> compClass = p.getObjClass().getComponentType();
BeanExpr initExpr = new NewArrayExpr(compClass, len);
doSetObjInitExpr(objInfo, initExpr);
BeanExpr lhsArrayExpr = objToLhsExpr(objInfo);
String arrayEltNamePrefix = objInfo.getVarNameWithSuffix("Elt");
for(int i = 0; i < len; i++) {
AbstractObjectValueHolder eltVH = eltsVH[i];
// *** recurse ***
BeanExpr eltExpr = objToLhsExpr(eltVH, arrayEltNamePrefix);
// test if it is not necessary to set default values: null
boolean setToDefault = (eltExpr == null ||
((eltExpr instanceof LiteralExpr) && ((LiteralExpr)eltExpr).getValue() == null));
if (!setToDefault) {
// stmt for "array[i] = expr"
ExprStmt assignIndexStmt = newAssignArrayIndexStmt(lhsArrayExpr, i, eltExpr);
objInfo.addInitStmt(assignIndexStmt);
}
}
return initExpr;
}
private ExprStmt newAssignArrayIndexStmt(BeanExpr lhsArrayExpr, int index, BeanExpr eltExpr) {
IndexedArrayExpr lhs = new IndexedArrayExpr(lhsArrayExpr, new LiteralExpr(index));
ExprStmt assignIndexStmt = new ExprStmt(new AssignExpr(lhs, eltExpr));
return assignIndexStmt;
}
@Override
public BeanAST casePrimitiveArrayElt(PrimitiveArrayEltValueHolder<?> p, ObjectStmtInfo objInfo) {
// NOT USED ... see casePrimitiveArray()
return null;
}
@Override
public BeanAST caseRefArrayElt(ArrayEltRefValueHolder p, ObjectStmtInfo objInfo) {
// NOT USED ... see caseRefArray()
return null;
}
// -------------------------------------------------------------------------
protected ObjectStmtInfo objToInitInfo(AbstractObjectValueHolder objVH, String optGeneratePrefixName) {
if (objVH == null) {
return null;
}
ObjectStmtInfo res = objStmtInfoMap.get(objVH);
if (res == null) {
res = new ObjectStmtInfo(objVH);
objStmtInfoMap.put(objVH, res);
if (optGeneratePrefixName != null) {
checkGeneratedVarName(res, optGeneratePrefixName);
}
// *** recurse (non lazy) ****
BeanExpr initExpr = (BeanExpr) objVH.visit(this, res);
doSetObjInitExpr(res, initExpr); // ... TOCHECK already set?
}
return res;
}
protected void doSetObjInitExpr(ObjectStmtInfo res, BeanExpr initExpr) {
Class<?> objType = res.getObjectVH().getObjClass();
res.setTypeAndInitExpr(objType, initExpr);
}
protected ObjectStmtInfo objToInitInfo(AbstractObjectValueHolder objVH) {
ObjectStmtInfo res = objToInitInfo(objVH, null);
return res;
}
protected void checkGeneratedVarName(ObjectStmtInfo objInfo, String optGeneratePrefixName) {
if (objInfo == null) {
return;
}
if (objInfo.getVarName() == null) {
String varName = nameGenerator.newName(optGeneratePrefixName);
objInfo.setVarName(varName);
}
}
protected BeanExpr objToLhsExpr(AbstractObjectValueHolder objVH, String optGeneratePrefixName) {
if (objVH == null) return new LiteralExpr(null);
ObjectStmtInfo objInfo = objToInitInfo(objVH);
if (objInfo.getVarName() == null) {
if (optGeneratePrefixName == null) {
optGeneratePrefixName = "tmp";
}
checkGeneratedVarName(objInfo, optGeneratePrefixName);
}
return objToLhsExpr(objInfo);
}
protected BeanExpr objToLhsExpr(ObjectStmtInfo objInfo) {
if (objInfo == null) {
return new LiteralExpr(null);
}
return new SimpleNameExpr(objInfo.getVarDeclStmt());
}
private NewObjectExpr doNewDefaultObjInstanceExpr(
ObjectStmtInfo objInfo,
AbstractObjectValueHolder p,
BeanExpr... optArgs
) {
if (objInfo.getVarName() == null) {
String classAlias = nameGenerator.classToAlias(p.getObjClass());
checkGeneratedVarName(objInfo, classAlias);
}
NewObjectExpr initExpr = new NewObjectExpr(p.getObjClass(), optArgs);
return initExpr;
}
private NewObjectExpr doNewDefaultObjInstanceExpr(
ObjectStmtInfo objInfo,
AbstractObjectValueHolder p,
List<BeanExpr> optArgs
) {
if (objInfo.getVarName() == null) {
String classAlias = nameGenerator.classToAlias(p.getObjClass());
checkGeneratedVarName(objInfo, classAlias);
}
NewObjectExpr initExpr = new NewObjectExpr(p.getObjClass(), optArgs);
return initExpr;
}
public static PropertyDescriptor findPropertyDesc(Field field) {
PropertyDescriptor res = null;
String fieldName = field.getName();
if (fieldName.startsWith("_")) {
fieldName = fieldName.substring(1); // standard convention on field "_foo" => getFoo()...
}
Class<?> beanClass = field.getDeclaringClass();
BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(beanClass);
} catch(Exception ex) {
return null; // ??? SHOULD NOT OCCUR
}
for(PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
if (prop.getName().equals(fieldName)) {
res = prop;
break;
}
}
return res;
}
private BeanStmt newLogWarnStmt(String msg) {
if (logVarDeclStmt == null) {
logVarDeclStmt = new VarDeclStmt(Logger.class, "log", null);
}
BeanExpr logFieldExpr = new SimpleNameExpr(logVarDeclStmt);
String logMethName = "warn";
BeanExpr logArgMsg = new LiteralExpr(msg);
BeanExpr methExpr = new MethodApplyExpr(logFieldExpr, logMethName, logArgMsg);
return new ExprStmt(methExpr );
}
public VarDeclStmt getLogVarDeclStmt() {
return logVarDeclStmt;
}
}