/************************************************************************
* Copyright (c) 2015-2016 IoT-Solutions e.U.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
************************************************************************/
package iot.jcypher.domain.genericmodel.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import iot.jcypher.domain.genericmodel.DOField;
import iot.jcypher.domain.genericmodel.DOType;
import iot.jcypher.domain.genericmodel.DOType.Builder;
import iot.jcypher.domain.genericmodel.DOType.DOClassBuilder;
import iot.jcypher.domain.genericmodel.DOType.DOEnumBuilder;
import iot.jcypher.domain.genericmodel.DOType.DOInterfaceBuilder;
import iot.jcypher.domain.genericmodel.DOType.Kind;
import iot.jcypher.domain.genericmodel.DOTypeBuilderFactory;
import iot.jcypher.domain.genericmodel.DomainObject;
import iot.jcypher.domain.genericmodel.InternalAccess;
import iot.jcypher.domain.internal.DomainAccess;
import iot.jcypher.domain.internal.IIntDomainAccess;
import iot.jcypher.domain.mapping.surrogate.AbstractSurrogate;
import iot.jcypher.graph.GrNode;
import iot.jcypher.graph.GrProperty;
import iot.jcypher.query.api.IClause;
import iot.jcypher.query.factories.clause.DO;
import iot.jcypher.query.factories.clause.MERGE;
import iot.jcypher.query.factories.clause.RETURN;
import iot.jcypher.query.factories.clause.WITH;
import iot.jcypher.query.values.JcNode;
import iot.jcypher.query.values.JcNumber;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
public class DomainModel {
private static final String JavaPkg = "java.";
private static final String[] primitives = new String[] { "int", "boolean",
"long", "short", "float", "double" };
private static final String EnumVals = "$VALUES"; // "ENUM$VALUES";
private static final String TypeNodePostfix = "_mdl";
private static final String Colon = ":";
private static final String propTypeName = "typeName";
private static final String propSuperTypeName = "superTypeName";
private static final String propInterfaceNames = "interfaceNames";
private static final String propFields = "fields";
private static final String propKind = "kind";
private static Map<String, ClassPool> classPools = new HashMap<String, ClassPool>();
private String domainName;
private String typeNodeName;
private Map<String, DOType> doTypes;
private List<DOType> unsaved;
private TypeBuilderFactory typeBuilderFactory;
private DomainAccess domainAccess;
private Map<Object, DomainObject> nursery;
private ThreadLocal<TransactionState> transactionState;
private int version;
DomainModel(String domName, String domLabel, DomainAccess domAccess) {
super();
this.domainName = domName;
this.typeNodeName = domLabel.concat(TypeNodePostfix);
this.doTypes = new HashMap<String, DOType>();
this.unsaved = new ArrayList<DOType>();
this.domainAccess = domAccess;
this.transactionState = new ThreadLocal<TransactionState>();
this.version = -1;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public DOType addType(Class<?> clazz) {
if (!AbstractSurrogate.class.isAssignableFrom(clazz)) {
String name = clazz.getName();
DOType doType;
if ((doType = this.doTypes.get(name)) == null) {
doType = InternalAccess.createDOType(name, this);
this.doTypes.put(name, doType);
boolean buildIn = doType.isBuildIn();
if (!buildIn) {
Builder builder = InternalAccess.createBuilder(doType);
Kind kind = clazz.isInterface() ? Kind.INTERFACE :
Enum.class.isAssignableFrom(clazz) ? Kind.ENUM : Modifier.isAbstract(clazz
.getModifiers()) ? Kind.ABSTRACT_CLASS : Kind.CLASS;
InternalAccess.setKind(builder, kind);
this.addToUnsaved(doType);
addFields(builder, clazz);
Class<?> sClass = clazz.getSuperclass();
DOType superType = null;
if (sClass != null)
superType = addType(sClass);
if (superType != null)
InternalAccess.setSuperType(builder, superType);
Class<?>[] ifss = clazz.getInterfaces();
if (ifss != null) {
for (Class<?> ifs : ifss) {
DOType interf = addType(ifs);
if (interf != null)
InternalAccess.addInterfaceUnique(doType, interf);
}
}
}
}
return doType;
}
return null;
}
private void addFields(Builder builder, Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!Modifier.isTransient(fields[i].getModifiers())) {
if (builder.build().getKind() == Kind.ENUM
&& fields[i].getName().indexOf(EnumVals) >= 0)
continue;
Class<?> fTyp = fields[i].getType();
String tName = fTyp.getName();
DOField fld = InternalAccess.createDOField(fields[i].getName(), tName, builder.build());
InternalAccess.addDeclaredFieldUnique(builder.build(), fld);
if (!builder.build().isBuildIn()) {
if (!fld.isBuidInType())
addType(fTyp);
if (List.class.isAssignableFrom(fields[i].getType())) {
boolean cTypeResolved = false;
Type gtype = fields[i].getGenericType();
if (gtype instanceof ParameterizedType) {
Type lType = ((ParameterizedType)gtype).getActualTypeArguments()[0];
String nm = lType instanceof Class<?> ? ((Class<?>)lType).getName() : null;
// add the component type if not build in
if (nm != null) {
if (!isBuildIn(nm))
addType((Class<?>)lType);
InternalAccess.setComponentTypeName(fld, nm);
cTypeResolved = true;
}
}
if (!cTypeResolved)
InternalAccess.setComponentTypeName(fld, DOField.COMPONENTTYPE_Object);
} else if (fields[i].getType().isArray()) {
Class<?> cType = fields[i].getType().getComponentType();
if (!isBuildIn(cType.getName()))
addType(cType);
InternalAccess.setComponentTypeName(fld, cType.getName());
}
}
}
}
}
public DOType getDOType(String typeName) {
return this.doTypes.get(typeName);
}
public List<DOType> getDOTypes() {
List<DOType> vals = new ArrayList<DOType>();
vals.addAll(this.doTypes.values());
Collections.sort(vals, new Comparator<DOType>() {
@Override
public int compare(DOType o1, DOType o2) {
return o1.getName().compareTo(o2.getName());
}
});
return vals;
}
public String getDomainName() {
return domainName;
}
public String getTypeNodeName() {
return this.typeNodeName;
}
public void mergeFrom(List<GrNode> mdlInfos) {
for (GrNode nd : mdlInfos) {
if (nd != null) {
GrProperty propTyp = nd.getProperty(propTypeName);
String typNm = propTyp.getValue().toString();
DOType doType = addType(typNm);
InternalAccess.setNodeId(doType, nd.getId());
setProperties(nd, doType);
}
}
}
public void loadFrom(List<GrNode> mdlInfos) {
for (GrNode nd : mdlInfos) {
if (nd != null) {
GrProperty propTyp = nd.getProperty(propTypeName);
String typNm = propTyp.getValue().toString();
DOType doType = addType(typNm);
InternalAccess.setNodeId(doType, nd.getId());
setProperties(nd, doType);
}
}
}
private void setProperties(GrNode nd, DOType doType) {
GrProperty propSuperTyp = nd.getProperty(propSuperTypeName);
GrProperty propFlds = nd.getProperty(propFields);
GrProperty propKnd = nd.getProperty(propKind);
GrProperty propIfss = nd.getProperty(propInterfaceNames);
Kind knd = Kind.valueOf(propKnd.getValue().toString());
Builder builder = InternalAccess.createBuilder(doType);
InternalAccess.setKind(builder, knd);
String sTypNm = propSuperTyp.getValue().toString();
if (!sTypNm.isEmpty())
InternalAccess.setSuperType(builder, addType(sTypNm));
Object flds = propFlds.getValue();
if (flds instanceof List<?>) {
for (Object obj : (List<?>) flds) {
String[] fld = obj.toString().split(":");
DOField doField = InternalAccess.createDOField(fld[0], fld[1], doType);
if (fld.length == 3) // a list or array type
InternalAccess.setComponentTypeName(doField, fld[2]);
InternalAccess.addDeclaredFieldUnique(doType, doField);
}
}
Object ifss = propIfss.getValue();
if (ifss instanceof List<?>) {
for (Object obj : (List<?>) ifss) {
InternalAccess.addInterfaceUnique(doType, addType(obj.toString()));
}
}
}
private DOType addType(String typeName) {
DOType typ = this.doTypes.get(typeName);
if (typ == null) {
typ = InternalAccess.createDOType(typeName, this);
this.doTypes.put(typeName, typ);
}
return typ;
}
public boolean hasChanged() {
return this.unsaved.size() > 0;
}
@SuppressWarnings("unchecked")
public List<IClause>[] getChangeClauses() {
List<IClause> clauses = null;
List<IClause> returnClauses = null;
List<IClause> withClauses = null;
if (hasChanged()) {
clauses = new ArrayList<IClause>();
returnClauses = new ArrayList<IClause>();
withClauses = new ArrayList<IClause>();
int idx = 0;
for (DOType t : this.unsaved) {
List<String> flds = new ArrayList<String>();
for (DOField f : t.getDeclaredFields()) {
StringBuilder sb = new StringBuilder();
sb.append(f.getName());
sb.append(Colon);
sb.append(f.getTypeName());
String ctn = f.getComponentTypeName();
if (ctn != null) {
sb.append(Colon);
sb.append(ctn);
}
flds.add(sb.toString());
}
List<String> ifss = new ArrayList<String>();
for (DOType ifs : t.getInterfaces()) {
String ifName = ifs.getName();
ifss.add(ifName);
}
String sTypeName = t.getSuperType() != null ? t.getSuperType()
.getName() : "";
String strIdx = String.valueOf(idx);
JcNode n = new JcNode("n_".concat(strIdx));
JcNumber nid = new JcNumber("nid_".concat(strIdx));
clauses.add(MERGE.node(n).label(getTypeNodeName())
.property(propTypeName).value(t.getName()));
clauses.add(DO.SET(n.property(propKind)).to(t.getKind()));
clauses.add(DO.SET(n.property(propSuperTypeName)).to(sTypeName));
clauses.add(DO.SET(n.property(propInterfaceNames)).to(ifss));
clauses.add(DO.SET(n.property(propFields)).to(flds));
returnClauses.add(RETURN.value(n.id()).AS(nid));
withClauses.add(WITH.value(n));
idx++;
}
return new List[] { clauses, returnClauses, withClauses };
}
return null;
}
public static boolean isBuildIn(String typeName) {
return typeName.startsWith(JavaPkg) || isPrimitive(typeName);
}
private static boolean isPrimitive(String typeName) {
for (String prim : primitives) {
if (prim.equals(typeName))
return true;
}
return false;
}
public List<DOType> getUnsaved() {
return unsaved;
}
public void addToUnsaved(DOType typ) {
if (this.unsaved.isEmpty())
this.version++;
if (!this.unsaved.contains(typ))
this.unsaved.add(typ);
TransactionState txState = this.transactionState.get();
if (txState != null) {
if (!txState.unsaved.contains(typ))
txState.unsaved.add(typ);
txState.version = this.version;
}
}
public void updatedToGraph() {
this.unsaved.clear();
}
public Class<?> getClassForName(String name) throws ClassNotFoundException {
Class<?> clazz;
try {
clazz = Class.forName(name);
} catch (ClassNotFoundException e) {
DOType doType = getDOType(name);
if (doType == null)
throw e;
try {
createClassFor(doType);
clazz = Class.forName(name);
} catch (Throwable e1) {
if (e1 instanceof ClassNotFoundException)
throw (ClassNotFoundException)e1;
else
throw new RuntimeException(e1);
}
}
return clazz;
}
public DomainObject getCreateDomainObjectFor(Object obj) {
DomainObject dobj = getNurseryObject(obj);
if (dobj == null) {
String typNm = obj.getClass().getName();
DOType typ = getDOType(typNm);
if (typ == null)
throw new RuntimeException("missing type: [".concat(typNm).concat("] in domain model"));
dobj = InternalAccess.createDomainObject(typ); // don't add to nursery
InternalAccess.setRawObject(dobj, obj);
} else
removeNurseryObject(obj);
return dobj;
}
private void createClassFor(DOType doType) throws Throwable {
createCtClassFor(doType, getClassPool());
}
private CtClass createCtClassFor(DOType doType, ClassPool cp)
throws Throwable {
CtClass cc = cp.getOrNull(doType.getName());
if (cc == null) {
if (doType.getKind() == Kind.INTERFACE) {
cc = cp.makeInterface(doType.getName());
} else {
cc = cp.makeClass(doType.getName());
if (doType.getKind() == Kind.ABSTRACT_CLASS)
cc.setModifiers(cc.getModifiers() | Modifier.ABSTRACT);
// if (doType.getKind() == Kind.ENUM) // enum modifier (see java.lang.Class)
// cc.setModifiers(cc.getModifiers() | javassist.Modifier.ENUM);
}
DOType doSType = doType.getSuperType();
if (doSType != null) {
CtClass scc = createCtClassFor(doSType, cp);
cc.setSuperclass(scc);
}
for (DOType ifs : doType.getInterfaces()) {
CtClass ifct = createCtClassFor(ifs, cp);
cc.addInterface(ifct);
}
if (doType.getKind() == Kind.ENUM) {
int count = 0;
for (DOField fld : doType.getDeclaredFields()) {
if (fld.getTypeName().equals(doType.getName()))
count++;
}
// enum values field
StringBuilder sb = new StringBuilder();
sb.append("private static ");
sb.append(doType.getName());
sb.append("[] values = new ");
sb.append(doType.getName());
sb.append('[');
sb.append(count);
sb.append("];");
CtField ctField = CtField.make(sb.toString(), cc);
cc.addField(ctField);
// enum values method
sb = new StringBuilder();
sb.append("public static ");
sb.append(doType.getName());
sb.append("[] ");
sb.append("values(){return values;}");
CtMethod mthd = CtMethod.make(sb.toString(), cc);
cc.addMethod(mthd);
// enum constructor
sb = new StringBuilder();
int idx = doType.getName().lastIndexOf('.');
String nm = idx >= 0 ? doType.getName().substring(idx + 1)
: doType.getName();
sb.append("public ");
sb.append(nm);
sb.append("(String name, int ordinal) {super(name, ordinal);}");
CtConstructor constr = CtNewConstructor.make(sb.toString(), cc);
cc.addConstructor(constr);
} else {
for (DOField fld : doType.getDeclaredFields()) {
CtField ctField;
String tn = fld.getTypeName();
if (!fld.isBuidInType()) {
DOType ft = getDOType(tn);
if (ft == null)
throw new ClassNotFoundException(tn);
CtClass ctFt = createCtClassFor(ft, cp);
ctField = new CtField(ctFt, fld.getName(), cc);
ctField.setModifiers(javassist.Modifier.PUBLIC);
} else {
StringBuilder sb = new StringBuilder();
sb.append("public ");
sb.append(tn);
sb.append(' ');
sb.append(fld.getName());
sb.append(';');
ctField = CtField.make(sb.toString(), cc);
}
cc.addField(ctField);
}
}
cc.toClass(); // creates the class and registers it with the class
// loader
if (doType.getKind() == Kind.ENUM) { // add the enum values
// dynamically
Class<?> clazz = Class.forName(doType.getName());
Field f = clazz.getDeclaredField("values");
f.setAccessible(true);
Object vals = f.get(clazz);
Constructor<?> cstr = clazz.getDeclaredConstructor(
String.class, int.class);
// ConstructorAccessor constr = ReflectionFactory.getReflectionFactory().newConstructorAccessor(cstr);
int ord = 0;
for (DOField fld : doType.getDeclaredFields()) {
if (fld.getTypeName().equals(doType.getName())) {
// Object val = constr.newInstance(new Object[]{fld.getName(), ord});
Object val = cstr.newInstance(fld.getName(), ord);
((Object[])vals)[ord] = val;
ord++;
}
}
// Object[] enums=clazz.getEnumConstants();
// clazz = clazz;
}
}
return cc;
}
private ClassPool getClassPool() {
ClassPool cp = classPools.get(this.getDomainName());
if (cp == null) {
cp = new ClassPool(true);
classPools.put(this.getDomainName(), cp);
}
return cp;
}
void addDOTypeIfNeeded(DOType doType) {
if (this.doTypes.get(doType.getName()) == null) {
this.doTypes.put(doType.getName(), doType);
if (!doType.isBuildIn())
this.addToUnsaved(doType);
}
}
public DOTypeBuilderFactory getTypeBuilderFactory() {
if (this.typeBuilderFactory == null)
this.typeBuilderFactory = new TypeBuilderFactory();
return this.typeBuilderFactory;
}
public void addNurseryObject(Object raw, DomainObject dobj) {
if (this.nursery == null)
this.nursery = new IdentityHashMap<Object, DomainObject>();
this.nursery.put(raw, dobj);
TransactionState txState = this.transactionState.get();
if (txState != null) {
if (txState.nursery == null)
txState.nursery = new IdentityHashMap<Object, DomainObject>();
txState.nursery.put(raw, dobj);
}
}
public DomainObject getNurseryObject(Object raw) {
if (this.nursery != null)
return this.nursery.get(raw);
return null;
}
public void removeNurseryObject(Object raw) {
if (this.nursery != null)
this.nursery.remove(raw);
}
public void clearNursery() {
if (this.nursery != null)
this.nursery.clear();
}
@SuppressWarnings("unchecked")
public void beginTx() {
if (this.transactionState.get() == null) {
TransactionState txState = new TransactionState();
txState.unsaved = (List<DOType>) ((ArrayList<DOType>) this.unsaved).clone();
if (this.nursery != null)
txState.nursery = (Map<Object, DomainObject>) ((IdentityHashMap<Object, DomainObject>)this.nursery).clone();
txState.version = this.version;
this.transactionState.set(txState);
}
}
public void closeTx(boolean failed) {
TransactionState txState = this.transactionState.get();
if (txState != null) {
if (failed) {
this.unsaved = txState.unsaved;
this.nursery = txState.nursery;
this.version = txState.version;
}
this.transactionState.remove();
}
}
DomainAccess getDomainAccess() {
return domainAccess;
}
public String asString() {
String indent = " ";
StringBuilder sb = new StringBuilder();
sb.append(this.domainName);
sb.append(" (DomainModel Version: ");
sb.append(this.version);
sb.append(", DomainInfo Version: ");
sb.append(((IIntDomainAccess)this.domainAccess).getInternalDomainAccess().getDomainInfoVersion());
sb.append(") {");
List<DOType> vals = getDOTypes();
for (DOType t : vals) {
sb.append('\n');
sb.append(t.asString(indent));
}
sb.append('\n');
sb.append('}');
return sb.toString();
}
public String nurseryAsString() {
if (this.nursery != null) {
List<String> keys = new ArrayList<String>();
Map<String, Integer> cont = new HashMap<String, Integer>();
Iterator<Object> it = this.nursery.keySet().iterator();
while(it.hasNext()) {
String nm = it.next().getClass().getName();
Integer cnt = cont.get(nm);
if (cnt == null)
cnt = new Integer(1);
else
cnt = new Integer(cnt.intValue() + 1);
cont.put(nm, cnt);
}
Iterator<Entry<String, Integer>> it_2 = cont.entrySet().iterator();
while(it_2.hasNext()) {
Entry<String, Integer> entry = (Entry<String, Integer>) it_2.next();
StringBuilder sb = new StringBuilder();
sb.append(entry.getKey());
sb.append('(');
sb.append(entry.getValue());
sb.append(')');
keys.add(sb.toString());
}
Collections.sort(keys);
return keys.toString();
}
return "null";
}
/********************************************/
public class TypeBuilderFactory implements DOTypeBuilderFactory {
@Override
public DOClassBuilder createClassBuilder(String typeName) {
DOClassBuilder ret = InternalAccess.createClassBuilder(typeName, DomainModel.this);
addDOTypeIfNeeded(ret.build());
DOType sType = getDOType("java.lang.Object");
if (sType == null) {
sType = InternalAccess.createDOType("java.lang.Object", DomainModel.this);
addDOTypeIfNeeded(sType);
}
ret.setSuperType(sType);
return ret;
}
@Override
public DOInterfaceBuilder createInterfaceBuilder(String typeName) {
DOInterfaceBuilder ret = InternalAccess.createInterfaceBuilder(typeName, DomainModel.this);
addDOTypeIfNeeded(ret.build());
return ret;
}
@Override
public DOEnumBuilder createEnumBuilder(String typeName) {
DOEnumBuilder ret = InternalAccess.createEnumBuilder(typeName, DomainModel.this);
addDOTypeIfNeeded(ret.build());
DOType sType = getDOType("java.lang.Enum");
if (sType == null) {
sType = InternalAccess.createDOType("java.lang.Enum", DomainModel.this);
addDOTypeIfNeeded(sType);
}
ret.setSuperType(sType);
return ret;
}
}
/********************************************/
private static class TransactionState {
private List<DOType> unsaved;
private Map<Object, DomainObject> nursery;
private int version;
}
}