Java tutorial
/* * Copyright 2015 Dmitry Vasilyev * * 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 com.trebogeer.jcql; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.ColumnDefinitions; import com.datastax.driver.core.ColumnMetadata; import com.datastax.driver.core.DataType; import com.datastax.driver.core.PlainTextAuthProvider; import com.datastax.driver.core.PreparedId; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Session; import com.datastax.driver.core.TableMetadata; import com.datastax.driver.core.TupleType; import com.datastax.driver.core.TupleValue; import com.datastax.driver.core.UDTValue; import com.datastax.driver.core.UserType; import com.datastax.driver.mapping.annotations.Column; import com.datastax.driver.mapping.annotations.Table; import com.datastax.driver.mapping.annotations.UDT; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.sun.codemodel.*; import com.sun.codemodel.writer.SingleStreamCodeWriter; import org.javatuples.Pair; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import static com.sun.codemodel.ClassType.CLASS; import static com.sun.codemodel.ClassType.INTERFACE; import static com.sun.codemodel.JExpr.lit; import static com.sun.codemodel.JMod.FINAL; import static com.sun.codemodel.JMod.NONE; import static com.sun.codemodel.JMod.PRIVATE; import static com.sun.codemodel.JMod.PUBLIC; import static com.sun.codemodel.JMod.STATIC; import static com.trebogeer.jcql.JCQLUtils.camelize; import static com.trebogeer.jcql.JCQLUtils.getDataMethod; import static com.trebogeer.jcql.JCQLUtils.getFullClassName; import static com.trebogeer.jcql.JCQLUtils.getTupleClass; import static com.trebogeer.jcql.JCQLUtils.getType; import static com.trebogeer.jcql.JCQLUtils.isInteger; import static com.trebogeer.jcql.JCQLUtils.setDataMethod; import static com.trebogeer.jcql.JCQLUtils.typeToDTStaticMthod; /** * @author <a href="http://github.com/trebogeer">Dmitry Vasilyev</a> */ public class JCQLMain { private static final Logger logger = LoggerFactory.getLogger("JCQL.LOG"); private final Options cfg; private final JCodeModel model; private final Iterator<Integer> seq = new Iterator<Integer>() { int s = 0; @Override public boolean hasNext() { return s < Integer.MAX_VALUE; } @Override public Integer next() { return ++s; } }; private JCQLMain(Options cfg) { this.cfg = cfg; this.model = new JCodeModel(); } public static void main(String[] args) { Options cfg = new Options(); CmdLineParser parser = new CmdLineParser(cfg); try { parser.parseArgument(args); } catch (CmdLineException e) { throw new RuntimeException(e); } final JCQLMain jcql = new JCQLMain(cfg); try { jcql.exec(); logger.info("Done!"); } catch (IOException e) { logger.error("Failed to write generated code due to : ", e); } } /** * main routine generating java code * * @throws IOException thrown if unable to write generated code to files */ public void exec() throws IOException { String keyspace = cfg.keysapce; Cluster.Builder b = Cluster.builder().addContactPoint(cfg.dbHost).withPort(Integer.valueOf(cfg.dbPort)); if (cfg.userName != null && !"".equals(cfg.userName.trim()) && cfg.password != null && !"".equals(cfg.password.trim())) { b = b.withAuthProvider(new PlainTextAuthProvider(cfg.userName, cfg.password)); } else { logger.info("No auth will be used. Either credentials are not provided or are incorrect."); } try (Cluster c = b.build(); Session s = c.connect(keyspace)) { Multimap<String, Pair<String, DataType>> beans = HashMultimap.create(); Multimap<String, Pair<String, ColumnMetadata>> tables = HashMultimap.create(); ArrayListMultimap<String, String> partitionKeys = ArrayListMultimap.create(); Collection<UserType> types = s.getCluster().getMetadata().getKeyspace(keyspace).getUserTypes(); for (UserType t : types) { String name = t.getTypeName(); Set<Pair<String, DataType>> fields = new HashSet<Pair<String, DataType>>(); for (String field : t.getFieldNames()) { DataType dt = t.getFieldType(field); fields.add(Pair.with(field, dt)); } beans.putAll(name, fields); } Collection<TableMetadata> tbls = s.getCluster().getMetadata().getKeyspace(keyspace).getTables(); for (TableMetadata t : tbls) { String name = t.getName(); for (ColumnMetadata clmdt : t.getPartitionKey()) { partitionKeys.put(name, clmdt.getName()); } partitionKeys.trimToSize(); Set<Pair<String, ColumnMetadata>> fields = new HashSet<>(); for (ColumnMetadata field : t.getColumns()) { fields.add(Pair.with(field.getName(), field)); } tables.putAll(name, fields); } generateModelCode(beans, tables, partitionKeys); generateAccessCode(s); if ("y".equalsIgnoreCase(cfg.toString)) { toStringMethods(); } if ("y".equalsIgnoreCase(cfg.printInfo)) { info(); } if ("y".equalsIgnoreCase(cfg.debug)) { model.build(new SingleStreamCodeWriter(System.out)); } else { File source = new File(cfg.generatedSourceDir); if (source.exists() || source.mkdirs()) { model.build(new File(cfg.generatedSourceDir)); } } } } /** * Reads queries and metadata from .yml file and generates java data access layer based on cassandra schema * and obtained metadata. * * @param s cassandra session to use for query execution * @throws IOException thrown if .yml file does not exist or due t inability to read it */ private void generateAccessCode(Session s) throws IOException { if (cfg.cqlFile != null && !"".equals(cfg.cqlFile.trim())) { File cql = new File(cfg.cqlFile); if (!cql.exists() || !cql.isFile()) { throw new IOException(String.format( "CQL file specified '%s' either " + "does not exist or is not a file.", cfg.cqlFile)); } try (InputStream is = new FileInputStream(cql)) { Yaml yaml = new Yaml(); Iterable<Object> it = yaml.loadAll(is); try { JDefinedClass dao = model._class(PUBLIC, cfg.jpackage + "." + camelize(cfg.keysapce) + "DAO", CLASS); JFieldVar session_ = dao.field(JMod.FINAL | JMod.PRIVATE, Session.class, "session"); JMethod constructor = dao.constructor(PUBLIC); JVar param = constructor.param(Session.class, "session"); constructor.body().assign(JExpr._this().ref(session_), param); for (Object data : it) { if (data instanceof LinkedHashMap) { LinkedHashMap<String, String> map = (LinkedHashMap<String, String>) data; if (!map.isEmpty()) { String name = map.entrySet().iterator().next().getKey(); String cqlStatement = map.entrySet().iterator().next().getValue(); if (!"schema".equalsIgnoreCase(name)) { PreparedStatement ps = s.prepare(cqlStatement); ColumnDefinitions ds = ps.getVariables(); PreparedId meta = ps.getPreparedId(); // Due to some reason datastax does not want to expose metadata the same way as JDBC does // See - https://datastax-oss.atlassian.net/browse/JAVA-195 // Ok, reflection then ColumnDefinitions metadata = null; ColumnDefinitions resultSetMetadata = null; try { Class<?> c = PreparedId.class; Field metadata_F = c.getDeclaredField("metadata"); metadata_F.setAccessible(true); Object o = metadata_F.get(meta); if (o instanceof ColumnDefinitions) { metadata = (ColumnDefinitions) o; } else { throw new Exception("metadata is not an instance of ColumnDefinitions"); } Field metadata_RS = c.getDeclaredField("resultSetMetadata"); metadata_RS.setAccessible(true); o = metadata_RS.get(meta); if (o instanceof ColumnDefinitions) { resultSetMetadata = (ColumnDefinitions) o; } } catch (Exception e) { throw new RuntimeException( "Failed to access metadata or resultsetMetaData of prepared statement.", e); } String table = resultSetMetadata == null ? metadata.getTable(0) : resultSetMetadata.getTable(0); JMethod method = dao.method(PUBLIC, model.ref(getFullClassName(cfg.jpackage, table)), camelize(name, true)); for (ColumnDefinitions.Definition cd : ds) { method.param(cd.getType().asJavaClass(), camelize(cd.getName(), true)); } method.body()._return(JExpr._null()); } } } } } catch (JClassAlreadyExistsException e) { throw new RuntimeException("Failed to generate Data Access Object", e); } } } } /** * Generates java model (pojos) from existing cassandra CQL schema. * // TODO - segregate mappers from pojos and make them separately configurable via options. The whole stack of generated code might not always be needed. * * @param beans udt definitions * @param tables table definitions * @param partitionKeys partition keys from table metadata */ private void generateModelCode(Multimap<String, Pair<String, DataType>> beans, Multimap<String, Pair<String, ColumnMetadata>> tables, ArrayListMultimap<String, String> partitionKeys) { JDefinedClass rowMapper; JDefinedClass toUDTMapper = null; JDefinedClass binder = null; String commonsPackage = (cfg.cpackage != null && !"".equals(cfg.cpackage)) ? cfg.cpackage : cfg.jpackage; try { rowMapper = model._class(PUBLIC, commonsPackage + ".RowMapper", INTERFACE); rowMapper._extends(model.ref(Serializable.class)); JTypeVar jtv = rowMapper.generify("T"); JTypeVar jtvRow = rowMapper.generify("R").bound(model.ref(com.datastax.driver.core.GettableData.class)); rowMapper.method(NONE, jtv, "map").param(jtvRow, "data"); } catch (Exception e) { throw new RuntimeException("Failed to generate mapper interface.", e); } if (tables != null && !tables.isEmpty()) { try { binder = model._class(PUBLIC, commonsPackage + ".TableBindMapper", INTERFACE); binder._extends(model.ref(Serializable.class)); JTypeVar jtv = binder.generify("T"); JMethod jm = binder.method(NONE, model.VOID, "bind"); jm.param(jtv, "data"); jm.param(model.ref(BoundStatement.class), "st"); jm.param(model.ref(Session.class), "session"); } catch (Exception e) { throw new RuntimeException("Failed to generate table bind interface.", e); } } if (beans != null && beans.size() > 0) { try { toUDTMapper = model._class(PUBLIC, commonsPackage + ".BeanToUDTMapper", INTERFACE); toUDTMapper._extends(model.ref(Serializable.class)); JTypeVar jtv = toUDTMapper.generify("T"); JMethod toUDT = toUDTMapper.method(NONE, model.ref(UDTValue.class), "toUDT"); JVar toUDTArg0 = toUDT.param(jtv, "data"); JVar toUDTArg1 = toUDT.param(Session.class, "session"); } catch (JClassAlreadyExistsException e) { throw new RuntimeException("Failed to generate UDT mapper interface.", e); } } if (beans != null) { for (String cl : beans.keySet()) { try { String camName = camelize(cl); JDefinedClass clazz = JCQLUtils.getBeanClass(cfg.jpackage, camName, model); clazz.field(PRIVATE | STATIC | FINAL, model.LONG, "serialVersionUID", JExpr.lit((long) ((cfg.jpackage + "." + camName).hashCode()))); // row mapper rowMapperCode(clazz, rowMapper, beans.get(cl), model.ref(com.datastax.driver.core.GettableData.class)); // pojo to UDT mapper toUDTMapperCode(clazz, toUDTMapper, beans.get(cl), cl); // fields/getters/setters/annotations clazz.annotate(UDT.class).param("keyspace", cfg.keysapce).param("name", cl); // JExpr.newArray(codeModel.ref(String.class)).add(ID).add(CODE).add(NAME) for (Pair<String, DataType> field : beans.get(cl)) { javaBeanFieldWithGetterSetter(clazz, field.getValue1(), field.getValue0(), -1, com.datastax.driver.mapping.annotations.Field.class); } } catch (JClassAlreadyExistsException e) { logger.warn("Class '{}' already exists for UDT, skipping ", cl); } } } if (tables != null && !tables.isEmpty()) { for (String table : tables.keySet()) { try { String camName = camelize(table); JDefinedClass clazz = JCQLUtils.getBeanClass(cfg.jpackage, camName, model); clazz.field(PRIVATE | STATIC | FINAL, model.LONG, "serialVersionUID", JExpr.lit((long) ((cfg.jpackage + "." + camName).hashCode()))); Collection<Pair<String, DataType>> dataTypes = Collections2 .filter(Collections2.transform(tables.get(table), new Function<Pair<String, ColumnMetadata>, Pair<String, DataType>>() { @Override public Pair<String, DataType> apply(Pair<String, ColumnMetadata> input) { return Pair.with(input.getValue0(), input.getValue1().getType()); } }), input -> input != null && !"solr_query".equalsIgnoreCase(input.getValue0())); // row mapper rowMapperCode(clazz, rowMapper, dataTypes, model.ref(com.datastax.driver.core.Row.class)); // bind to statement code binderToStatemet(clazz, binder, dataTypes); // fields/getters/setters/annotations clazz.annotate(Table.class).param("keyspace", cfg.keysapce).param("name", table); List<String> pkList = partitionKeys.get(table); Set<String> pks = new HashSet<>(pkList); for (Pair<String, ColumnMetadata> field : tables.get(table)) { String fieldName = field.getValue0(); int order = -1; if (pks.contains(fieldName)) { order = 0; if (pks.size() > 1) { order = pkList.indexOf(field.getValue0()); } } javaBeanFieldWithGetterSetter(clazz, field.getValue1().getType(), fieldName, order, Column.class); } } catch (JClassAlreadyExistsException ex) { logger.warn("Class '{}' already exists for table, skipping ", table); } } } } /** * Binds POJO to BoundStatement basing on PreparedStatement metadata by * introspecting ColumnDefinitions and invoking BoundStatement#bind(...) * * @param clazz pojo class * @param binder binder interface * @param dataTypes collection of names and DataTypes of pojo fields */ private void binderToStatemet(JDefinedClass clazz, JDefinedClass binder, Collection<Pair<String, DataType>> dataTypes) throws JClassAlreadyExistsException { JClass bindMapperNarrowed = binder.narrow(clazz); JDefinedClass bindImpl = clazz._class(JMod.FINAL | JMod.STATIC | JMod.PRIVATE, clazz.name() + "BindMapper") ._implements(bindMapperNarrowed); bindImpl.field(PRIVATE | STATIC | FINAL, model.LONG, "serialVersionUID", JExpr.lit((long) ((cfg.jpackage + "." + (clazz.name() + "BindMapper")).hashCode()))); JVar bindSt = clazz.field(JMod.PRIVATE | JMod.STATIC | JMod.FINAL, bindImpl, "bind", JExpr._new(bindImpl)); clazz.method(PUBLIC | JMod.STATIC, binder.narrow(clazz), "bind").body()._return(bindSt); JMethod bind = bindImpl.method(PUBLIC, model.VOID, "bind"); JVar dataBind = bind.param(clazz, "data"); JVar st = bind.param(BoundStatement.class, "st"); JVar session = bind.param(Session.class, "session"); JBlock body = bind.body(); // TODO throw exception may be instead returning body._if(dataBind.eq(JExpr._null()))._then()._return(); body._if(st.eq(JExpr._null()))._then()._throw(JExpr._new(model.ref(IllegalArgumentException.class)) .arg("Cassandra BoundStatement can't be null.")); JVar defsMap = body.decl(model.ref(Map.class).narrow(String.class, Integer.class), "defSet", JExpr._new(model.ref(HashMap.class).narrow(String.class, Integer.class))); JVar columnDefinitions = body.decl(model.ref(ColumnDefinitions.class), "cds", st.invoke("preparedStatement").invoke("getVariables")); JVar cnt = body.decl(model.INT, "cnt", JExpr.lit(0)); JForEach forEach0 = body.forEach(model.ref(ColumnDefinitions.Definition.class), "entry", columnDefinitions.invoke("asList")); JVar entry0 = forEach0.var(); JBlock forEachBody0 = forEach0.body(); forEachBody0.add(defsMap.invoke("put").arg(entry0.invoke("getName")).arg(cnt)); forEachBody0.assignPlus(cnt, JExpr.lit(1)); /*JVar bindArgs = body.decl(model.ref(Object[].class), "bindArgs", JExpr.newArray(model.ref(Object.class), defsMap.invoke("size"))); */ for (Pair<String, DataType> field : dataTypes) { DataType dt = field.getValue1(); String fname = field.getValue0(); JBlock ifBody = body._if(defsMap.invoke("containsKey").arg(JExpr.lit(fname)))._then(); JVar pos = ifBody.decl(model.INT, "pos", defsMap.invoke("get").arg(JExpr.lit(fname))); JVar fv = ifBody.decl(getType(dt, model, cfg), camelize(fname, true) + "Val", dataBind.invoke("get" + camelize(fname))); JConditional ifNull = ifBody._if(fv.ne(JExpr._null())); JBlock ifNullBody = ifNull._then(); JExpression rvar = processMapField(dt, dataBind, fname, session, ifNullBody); // TODO set individual fields instead directly to statement //ifNullBody.assign(JExpr.component(bindArgs, pos), rvar); ifNullBody.add(st.invoke(setDataMethod(dt.getName())).arg(pos).arg(rvar)); JBlock elseBody = ifNull._else(); elseBody.add(st.invoke("setToNull").arg(fname)); } // body.add(st.invoke("bind").arg(bindArgs)); } /** * Maps pojo to UDT for subsequent update/insert. * * @param clazz pojo class * @param udtMapper to udt mapper interface * @param fields collection of pojo fields * @param name name of user type in cassandra ddl * @throws JClassAlreadyExistsException thrown if mapper already exists */ private void toUDTMapperCode(JDefinedClass clazz, JClass udtMapper, Collection<Pair<String, DataType>> fields, String name) throws JClassAlreadyExistsException { JClass udtMapperNarrowed = udtMapper.narrow(clazz); JDefinedClass mapperImpl = clazz ._class(JMod.FINAL | JMod.STATIC | JMod.PRIVATE, clazz.name() + "ToUDTMapper") ._implements(udtMapperNarrowed); mapperImpl.field(PRIVATE | STATIC | FINAL, model.LONG, "serialVersionUID", JExpr.lit((long) ((cfg.jpackage + "." + (clazz.name() + "ToUDTMapper")).hashCode()))); JVar udtMapperSt = clazz.field(JMod.PRIVATE | JMod.STATIC | JMod.FINAL, mapperImpl, "udt_mapper", JExpr._new(mapperImpl)); clazz.method(PUBLIC | JMod.STATIC, udtMapperNarrowed, "udtMapper").body()._return(udtMapperSt); JMethod toUDT = mapperImpl.method(PUBLIC, model.ref(UDTValue.class), "toUDT"); JVar dataUdt = toUDT.param(clazz, "data"); JVar session = toUDT.param(Session.class, "session"); JBlock body = toUDT.body(); body._if(dataUdt.eq(JExpr._null()))._then()._return(JExpr._null()); body._if(session.eq(JExpr._null()))._then()._throw( JExpr._new(model.ref(IllegalArgumentException.class)).arg("Cassandra Session can't be null.")); JVar userType = body.decl(model.ref(UserType.class), "userType", session.invoke("getCluster").invoke("getMetadata").invoke("getKeyspace").arg(lit(cfg.keysapce)) .invoke("getUserType").arg(lit(name))); JVar udt = body.decl(model.ref(UDTValue.class), "udtValue", userType.invoke("newValue")); for (Pair<String, DataType> field : fields) { DataType dt = field.getValue1(); String fname = field.getValue0(); JVar fv = body.decl(getType(dt, model, cfg), camelize(fname, true) + "Val", dataUdt.invoke("get" + camelize(fname))); JConditional ifNull = body._if(fv.ne(JExpr._null())); JBlock ifNullBody = ifNull._then(); JExpression arg2 = processMapField(dt, dataUdt, fname, session, ifNullBody); ifNullBody.add(udt.invoke(setDataMethod(dt.getName())).arg(lit(fname)).arg(arg2)); JBlock elseBody = ifNull._else(); elseBody.add(udt.invoke("setToNull").arg(fname)); } body._return(udt); } private JExpression processMapField(DataType dt, JVar data, String name, JVar session, JBlock body) { String fname = name; String fnamec = camelize(fname); String fnamecl = camelize(fname, true); if (dt.isCollection()) { List<DataType> argTypes = dt.getTypeArguments(); if (argTypes != null) { if (argTypes.size() == 1) { DataType argDt = argTypes.get(0); if (argDt.isCollection()) { // TODO cassandra does not support embedded collections yet but might support in future throw new UnsupportedOperationException("Collections of collections are not" + " supported within UDTs and probably by cassandra 2.1."); } else if (argDt.isFrozen()) { if (argDt instanceof UserType) { UserType ut = (UserType) argDt; String utname = ut.getTypeName(); String utnamec = camelize(utname); JClass sourceCollectionGeneric = model.ref(getFullClassName(cfg.jpackage, utnamec)); JClass sourceCollectionClass = model.ref(dt.asJavaClass()) .narrow(sourceCollectionGeneric); JVar source = body.decl(sourceCollectionClass, fnamecl + "Source", data.invoke("get" + camelize(fname))); JVar target = body.decl(model.ref(dt.asJavaClass()).narrow(UDTValue.class), fnamecl + "Target", JExpr._new(dt.asJavaClass().isAssignableFrom(Set.class) ? model.ref(HashSet.class).narrow(UDTValue.class) : model.ref(LinkedList.class).narrow(UDTValue.class))); JForEach forEach = body.forEach(sourceCollectionGeneric, "entry", source); JVar entry = forEach.var(); JBlock forEachBody = forEach.body(); JInvocation convertToUDT = sourceCollectionGeneric.staticInvoke("udtMapper") .invoke("toUDT").arg(entry).arg(session); forEachBody.add(target.invoke("add").arg(convertToUDT)); return target; } else if (argDt instanceof TupleType) { // TODO support tuples throw new UnsupportedOperationException( "Collections of tuples within " + "UDT are not yet supported."); } } } else if (argTypes.size() == 2) { DataType argDt0 = argTypes.get(0); DataType argDt1 = argTypes.get(1); JClass argc0 = getType(argDt0, model, cfg); JClass argc1 = getType(argDt1, model, cfg); if (argDt0.isCollection() || argDt1.isCollection()) { // TODO cassandra does not support embedded collections yet but might support in future throw new UnsupportedOperationException("Collections of collections are not" + " supported within UDTs and probably by cassandra 2.1."); } else if (argDt0.isFrozen() || argDt1.isFrozen()) { if (argDt0 instanceof TupleType || argDt1 instanceof TupleType) { throw new UnsupportedOperationException( "Collections of tuples are not yet" + " supported within UDTs."); } JVar source = body.decl(model.ref(Map.class).narrow(argc0, argc1), fnamecl + "Source", data.invoke("get" + camelize(fname))); JVar target = body.decl( model.ref(Map.class).narrow(argDt0.asJavaClass(), argDt1.asJavaClass()), fnamecl + "Target", JExpr._new(model.ref(HashMap.class).narrow(argDt0.asJavaClass(), argDt1.asJavaClass()))); JClass entryClass = model.ref(Map.Entry.class).narrow(argc0, argc1); JForEach forEach = body.forEach(entryClass, "entry", source.invoke("entrySet")); JVar entry = forEach.var(); JExpression k = entry.invoke("getKey"); JExpression v = entry.invoke("getValue"); JBlock forEachBody = forEach.body(); JExpression key = argDt0.isFrozen() ? argc0.staticInvoke("udtMapper").invoke("toUDT").arg(k).arg(session) : k; JExpression value = argDt1.isFrozen() ? argc1.staticInvoke("udtMapper").invoke("toUDT").arg(v).arg(session) : v; forEachBody.add(target.invoke("put").arg(key).arg(value)); return target; } } } } else if (dt.isFrozen()) { if (dt instanceof UserType) { return model.ref(getFullClassName(cfg.jpackage, ((UserType) dt).getTypeName())) .staticInvoke("udtMapper").invoke("toUDT").arg(data.invoke("get" + fnamec)).arg(session); } else if (dt instanceof TupleType) { Pair<JExpression, JExpression> refVal = processTuple(dt, data, fnamec, body, session); return refVal.getValue1(); } } return data.invoke("get" + camelize(fname)); } private Pair<JExpression, JExpression> processTuple(DataType dt, JVar data, String fnamec, JBlock body, JVar session) { TupleType tt = (TupleType) dt; List<DataType> componentTypes = tt.getComponentTypes(); Class<?> tupleClass = getTupleClass(componentTypes.size()); JClass tuple = model.ref(tupleClass); JInvocation of = model.ref(TupleType.class).staticInvoke("of"); for (DataType adt : componentTypes) { tuple = tuple.narrow(getType(adt, model, cfg)); } JVar tupleRef = body.decl(tuple, camelize(fnamec, true) + "Tuple", data.invoke("get" + fnamec)); int i = 0; ArrayList<JExpression> getvalues = new ArrayList<>(componentTypes.size()); for (DataType adt : componentTypes) { JExpression jexpr = JExpr._null(); getvalues.add(i, tupleRef.invoke("getValue" + i)); if (adt.isFrozen()) { if (adt instanceof UserType) { UserType ut = (UserType) adt; String tname = ut.getTypeName(); JVar udtValue = body.decl(model.ref(UDTValue.class), camelize(tname, true), model.ref(getFullClassName(cfg.jpackage, tname)).staticInvoke("udtMapper") .invoke("toUDT").arg(tupleRef.invoke("getValue" + i)).arg(session)); JVar userTypeE = body.decl(model.ref(UserType.class), camelize(tname, true) + "UserType", udtValue.invoke("getType")); jexpr = userTypeE; getvalues.set(i, udtValue); } else if (adt instanceof TupleType) { Pair<JExpression, JExpression> rvPair = processTuple(adt, tupleRef, "Value" + i, body, session); JExpression ref = rvPair.getValue0(); JExpression value = rvPair.getValue1(); getvalues.set(i, value); jexpr = ref; // jexpr = JExpr._null(); } } else if (adt.isCollection()) { // TODO implement } else { jexpr = model.ref(DataType.class).staticInvoke(typeToDTStaticMthod(adt.getName())); } i++; of.arg(jexpr); } JVar ttE = body.decl(model.ref(TupleType.class), "tupleType" + seq.next(), of); JVar tvE = body.decl(model.ref(TupleValue.class), "tupleValue" + seq.next(), ttE.invoke("newValue")); for (int a = 0; a < getvalues.size(); a++) { body.add(tvE.invoke(setDataMethod(componentTypes.get(a).getName())).arg(JExpr.lit(a)) .arg(getvalues.get(a))); } return Pair.with(ttE, tvE); } /** * Generates private field, getter and setter, some cassandra annotations * * @param clazz pojo definition * @param dt data type of the field to be generated * @param name name of the filed * @param pko order of partition key if composite * @param ann either @Field or @Column datastax annotation * depending on whether table or udt is processed by method */ private void javaBeanFieldWithGetterSetter(JDefinedClass clazz, DataType dt, String name, int pko, Class<? extends Annotation> ann) { JClass ref = getType(dt, model, cfg); JFieldVar f = clazz.field(JMod.PRIVATE, ref, camelize(name, true)); if (ann != null) { f.annotate(ann).param("name", name); } if (dt.isFrozen()) { f.annotate(com.datastax.driver.mapping.annotations.Frozen.class); } if (pko == 0) { f.annotate(com.datastax.driver.mapping.annotations.PartitionKey.class); } else if (pko > 0) { f.annotate(com.datastax.driver.mapping.annotations.PartitionKey.class).param("value", pko); } clazz.method(PUBLIC, ref, "get" + camelize(name)).body()._return(JExpr._this().ref(f)); JMethod m = clazz.method(PUBLIC, Void.TYPE, "set" + camelize(name)); JVar p = m.param(ref, camelize(name, true)); m.body().assign(JExpr._this().ref(f), p); } /** * Generates row mapper code for a specified pojo * * @param clazz pojo class * @param rowMapper RowMapper interface * @param fields fields to map * @throws JClassAlreadyExistsException thrown if class already exists in code model */ private void rowMapperCode(JDefinedClass clazz, JClass rowMapper, Collection<Pair<String, DataType>> fields, JClass arg2) throws JClassAlreadyExistsException { JClass rowMapperNarrowed = rowMapper.narrow(clazz, arg2); JDefinedClass mapperImpl = clazz._class(JMod.FINAL | JMod.STATIC | JMod.PRIVATE, clazz.name() + "RowMapper") ._implements(rowMapperNarrowed); clazz.field(JMod.PRIVATE | JMod.STATIC | JMod.FINAL, mapperImpl, "mapper", JExpr._new(mapperImpl)); mapperImpl.field(PRIVATE | STATIC | FINAL, model.LONG, "serialVersionUID", JExpr.lit((long) ((cfg.jpackage + "." + (clazz.name() + "RowMapper")).hashCode()))); boolean isTableMapper = arg2.name().equalsIgnoreCase(com.datastax.driver.core.Row.class.getSimpleName()); JMethod map = mapperImpl.method(PUBLIC, clazz, "map"); JVar param = map.param(arg2, "data"); JBlock body = map.body(); body._if(param.eq(JExpr._null()))._then()._return(JExpr._null()); JVar columnDefinitions = null; if (isTableMapper) { columnDefinitions = body.decl(model.ref(ColumnDefinitions.class), "cdfs", param.invoke("getColumnDefinitions")); } JVar bean = body.decl(clazz, "entity", JExpr._new(clazz)); for (Pair<String, DataType> field : fields) { String name = field.getValue0(); DataType type = field.getValue1(); JExpression ifCond = JOp.not(param.invoke("isNull").arg(lit(name))); if (isTableMapper && columnDefinitions != null) { ifCond = JOp.cand(columnDefinitions.invoke("contains").arg(lit(name)), ifCond); } JBlock ifNotNullBody = body._if(ifCond)._then(); if (type.isCollection()) { /*JBlock jb = jb._if(JOp.not(param.invoke("isNull") .arg(lit(name))))._then();*/ List<DataType> typeArgs = type.getTypeArguments(); JExpression setterExpression; if (typeArgs.size() == 1) { DataType arg = typeArgs.get(0); JClass cl = getType(arg, model, cfg); JExpression tclass = arg.isFrozen() ? arg.asJavaClass().isAssignableFrom(UDTValue.class) ? model.ref(UDTValue.class).dotclass() : model.ref(TupleValue.class).dotclass() : cl.dotclass(); setterExpression = param.invoke(getDataMethod(type.getName())).arg(name).arg(tclass); if (arg.isFrozen()) { if (arg instanceof UserType) { JClass collectionClass = model .ref(type.asJavaClass().isAssignableFrom(List.class) ? LinkedList.class : HashSet.class) .narrow(cl); JVar collection = ifNotNullBody.decl(collectionClass, camelize(name, true), JExpr._new(collectionClass)); JClass udtValueCollection = model.ref(type.asJavaClass()).narrow(UDTValue.class); JVar udtCollection = ifNotNullBody.decl(udtValueCollection, camelize(name, true) + "Source", setterExpression); JForEach forEach = ifNotNullBody.forEach(model.ref(UDTValue.class), "entry", udtCollection); JVar var = forEach.var(); JBlock forEachBody = forEach.body(); forEachBody.add( collection.invoke("add").arg(cl.staticInvoke("mapper").invoke("map").arg(var))); setterExpression = collection; } else if (arg instanceof TupleType) { // TODO handle tuples throw new UnsupportedOperationException("Collections of tuples" + " are not yet supported when converting from Row to POJO."); } } } else if (typeArgs.size() == 2) { DataType arg0 = typeArgs.get(0); DataType arg1 = typeArgs.get(1); JClass argc0 = getType(arg0, model, cfg); JClass argc1 = getType(arg1, model, cfg); if (arg0.isFrozen() || arg1.isFrozen()) { JVar hashmap = ifNotNullBody.decl(model.ref(Map.class).narrow(argc0).narrow(argc1), camelize(name, true), JExpr._new(model.ref(HashMap.class).narrow(argc0).narrow(argc1))); JExpression arg0csClass = model.ref(arg0.asJavaClass()).dotclass(); JExpression arg1csClass = model.ref(arg1.asJavaClass()).dotclass(); JVar frozen = ifNotNullBody.decl( model.ref(Map.class).narrow(arg0.asJavaClass(), arg1.asJavaClass()), camelize(name, true) + "Source", param.invoke(getDataMethod(type.getName())) .arg(name).arg(arg0csClass).arg(arg1csClass)); JForEach forEach = ifNotNullBody.forEach( model.ref(Map.Entry.class).narrow(arg0.asJavaClass(), arg1.asJavaClass()), "entry", frozen.invoke("entrySet")); JVar var = forEach.var(); JBlock forEachBody = forEach.body(); forEachBody .add(hashmap.invoke("put") .arg(arg0.isFrozen() ? argc0.staticInvoke("mapper").invoke("map") .arg(var.invoke("getKey")) : var.invoke("getKey")) .arg(arg1.isFrozen() ? argc1.staticInvoke("mapper").invoke("map") .arg(var.invoke("getValue")) : var.invoke("getValue"))); setterExpression = hashmap; } else { setterExpression = param.invoke(getDataMethod(type.getName())).arg(name) .arg(argc0.dotclass()).arg(argc1.dotclass()); } } else { throw new RuntimeException(String.format("Unsupported arguments count %d: ", typeArgs.size())); } ifNotNullBody.add(bean.invoke("set" + camelize(name)).arg(setterExpression)); } else if (type.isFrozen()) { if (type instanceof UserType) { UserType ut = (UserType) type; ifNotNullBody.add(bean.invoke("set" + camelize(name)).arg(mapUDT(name, ut, param, type))); } else if (type instanceof TupleType) { TupleType tt = (TupleType) type; mapTuple(tt, ifNotNullBody, name, param, bean, type); } } else { ifNotNullBody.add(bean.invoke("set" + camelize(name)) .arg(param.invoke(getDataMethod(type.getName())).arg(name))); } } body._return(JExpr.direct("entity")); clazz.method(PUBLIC | JMod.STATIC, rowMapperNarrowed, "mapper").body()._return(JExpr.direct("mapper")); } /** * Maps tuple cassandra type to java tuple as a part of RowMapper#map(GettableData data) call * * @param tt cassandra tuple type * @param body body to append code to * @param name name of a filed of type tuple * @param param map method argument - raw casandra type * @param bean bean to invoke setter on * @param type */ private void mapTuple(TupleType tt, JBlock body, String name, JVar param, JVar bean, DataType type) { List<DataType> dt = tt.getComponentTypes(); JClass dts[] = new JClass[dt.size()]; for (int i = 0; i < dts.length; i++) { dts[i] = getType(dt.get(i), model, cfg); } JVar t = body.decl(model.ref(TupleValue.class), camelize(name, true), param.invoke(getDataMethod(type.getName())).arg(name)); JConditional iffy = body._if(t.ne(JExpr._null())); JBlock ifbody = iffy._then(); JInvocation tc = model.ref(getTupleClass(dts.length)).staticInvoke("with"); for (int i = 0; i < dts.length; i++) { DataType cdt = dt.get(i); if (cdt.isFrozen()) { if (cdt instanceof UserType) { UserType ut = (UserType) cdt; tc = tc.arg(mapUDT(i + "", ut, t, cdt)); } else if (cdt instanceof TupleType) { TupleType tuple = (TupleType) cdt; // TODO need to support nested tuples. Will do later. Passing Null for now. tc = tc.arg(JExpr.cast(getType(tuple, model, cfg), JExpr._null())); } } else if (cdt.isCollection()) { // TODO need to support nested collections within tuples. Will do later. Passing Null for now. tc = tc.arg(JExpr.cast(getType(cdt, model, cfg), JExpr._null())); } else { tc = tc.arg(t.invoke(getDataMethod(dt.get(i).getName())).arg(lit(i))); } } ifbody.add(bean.invoke("set" + camelize(name)).arg(tc)); } private JInvocation mapUDT(String name, UserType ut, JVar param, DataType type) { return model.ref(getFullClassName(cfg.jpackage, ut.getTypeName())).staticInvoke("mapper").invoke("map") .arg(param.invoke(getDataMethod(type.getName())) // somewhat very fragile and extremely straightforward but will stick with it form now .arg(isInteger(name) ? lit(Integer.valueOf(name)) : lit(name))); } private void info() { logger.info("=================================================================="); logger.info("Code Model Info :"); logger.info("Artifacts Count : {}", model.countArtifacts()); logger.info("Packages : "); Iterator<JPackage> it = model.packages(); while (it.hasNext()) { JPackage jp; logger.info((jp = it.next()).name()); Iterator<JDefinedClass> classIterator = jp.classes(); while (classIterator.hasNext()) logger.info("'- " + classIterator.next().fullName()); } logger.info("=================================================================="); } private void toStringMethods() { Iterator<JPackage> jPackageIterator = model.packages(); while (jPackageIterator.hasNext()) { Iterator<JDefinedClass> jDefinedClassIterator = jPackageIterator.next().classes(); while (jDefinedClassIterator.hasNext()) { JDefinedClass jdc = jDefinedClassIterator.next(); if (jdc.isClass() && !jdc.isInterface()) { JMethod jMethod = jdc.method(PUBLIC, model.ref(String.class), "toString"); JBlock body = jMethod.body(); JVar sb = body.decl(JMod.FINAL, model.ref(StringBuilder.class), "sb", JExpr._new(model.ref(StringBuilder.class))); body.add(sb.invoke("append").arg("{").invoke("append").arg(jdc.name()).invoke("append") .arg(":{")); int size = jdc.fields().size(); for (Map.Entry<String, JFieldVar> f : jdc.fields().entrySet()) { if (!f.getValue().type().fullName().contains("Mapper")) { body.add(sb.invoke("append").arg(f.getKey()).invoke("append").arg("=").invoke("append") .arg(f.getValue())); if (size != 1) { body.add(sb.invoke("append").arg(",")); } } size--; } body.add(sb.invoke("append").arg("}}")); body._return(sb.invoke("toString")); } } } } }