/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The name "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package org.apache.ws.jaxme.js.pattern;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ws.jaxme.js.DirectAccessible;
import org.apache.ws.jaxme.js.JavaComment;
import org.apache.ws.jaxme.js.JavaConstructor;
import org.apache.ws.jaxme.js.JavaField;
import org.apache.ws.jaxme.js.JavaInnerClass;
import org.apache.ws.jaxme.js.JavaMethod;
import org.apache.ws.jaxme.js.JavaQName;
import org.apache.ws.jaxme.js.JavaQNameImpl;
import org.apache.ws.jaxme.js.JavaSource;
import org.apache.ws.jaxme.js.LocalJavaField;
import org.apache.ws.jaxme.logging.Logger;
import org.apache.ws.jaxme.logging.LoggerAccess;
import org.apache.ws.jaxme.sqls.Column;
import org.apache.ws.jaxme.sqls.ColumnSet;
import org.apache.ws.jaxme.sqls.ForeignKey;
import org.apache.ws.jaxme.sqls.InsertStatement;
import org.apache.ws.jaxme.sqls.SelectStatement;
import org.apache.ws.jaxme.sqls.Table;
/** <p>The VersionGenerator is able to clone a version of a row
* in a database. That is nothing special. A simple INSERT does
* the same.</p>
* <p>The difference is that the VersionGenerator is able to
* clone rows in other tables referencing the cloned table
* as well, updating the references, and clone and update rows
* referencing these cloned and updated rows, and so on.</p>
* <p>In other words: The VersionGenerator derives a new
* version of a complex object stored in the database.</p>
* <p>The VersionGenerator operates on a list of tables. This
* list must not contain forward or self references. In other
* words: Under no circumstances may a table in the list contain
* a foreign key referencing another table, which follows later.</p>
*
* @author <a href="mailto:joe@ispsoft.de">Jochen Wiedmann</a>
*/
public class VersionGenerator {
/** <p>The ColumnUpdater is able to update one or more columns in a
* table. Such a column update is required, because the cloned tables
* must not have the same primary key.</p>
* <p>The typical use is to specify a ColumnUpdater for the head
* table, which bumps the version number. For any child table
* you could specify a ColumnUpdater which generates a new primary
* key.</p>
*/
public interface ColumnUpdater {
/** <p>Generates code for updating a table row. The row is supplied
* as an array of objects. The order of objects matches the columns
* of the given table.</p>
* @param pMethod The method being created; may be used to generate
* local fields, etc.
* @param pTableInfo The table being updated
* @param pMap A local Java field with a Map of rows, that have already
* been cloned. The keys and values are both instances of the generated
* inner class DataCache. The keys are holding the primary keys of
* the original rows (which have been cloned) and the values are the
* primary keys of the created rows (the generated clones).
* @param pConnection A local Java field with an open database connection
* @param pRow A local Java field with an array of values being cloned
*/
public void update(JavaMethod pMethod, TableInfo pTableInfo,
DirectAccessible pConnection,
DirectAccessible pMap,
DirectAccessible pRow);
}
/** <p>This class is used internally to maintain the informations on
* the tables being cloned.</p>
*/
public static class TableInfo {
private final Table table;
private final String propertyName;
private boolean isReferenced;
private boolean hasPrimaryKey;
private final List columnUpdaters = new ArrayList();
TableInfo(Table pTable, String pPropertyName) {
table = pTable;
propertyName = pPropertyName;
}
public Table getTable() { return table; }
public String getPropertyName() { return propertyName; }
public void add(ColumnUpdater pColumnUpdater) {
columnUpdaters.add(pColumnUpdater);
}
public Iterator getColumnUpdaters() { return columnUpdaters.iterator(); }
public void setReferenced(boolean pReferenced) {
isReferenced = true;
}
public boolean isReferenced() {
return isReferenced;
}
public boolean hasPrimaryKey() {
return hasPrimaryKey;
}
public void setPrimaryKey(boolean pPrimaryKey) {
hasPrimaryKey = pPrimaryKey;
}
}
/** <p>An implementation of {@link ColumnUpdater}, which updates foreign
* key references.</p>
*/
private class ForeignKeyUpdater implements ColumnUpdater {
private final ForeignKey foreignKey;
private final TableInfo referencedTable;
private ForeignKeyUpdater(ForeignKey pForeignKey, TableInfo pReferencedTable) {
foreignKey = pForeignKey;
referencedTable = pReferencedTable;
}
public void update(JavaMethod pMethod, TableInfo pTableInfo,
DirectAccessible pConnection,
DirectAccessible pMap, DirectAccessible pRow) {
LocalJavaField referencedKey = getCacheDataClassInstance(pMethod,
referencedTable,
foreignKey, pRow);
LocalJavaField mappedKey = pMethod.newJavaField(referencedKey.getType());
mappedKey.addLine("(", referencedKey.getType(), ") ", pMap, ".get(",
referencedKey, ")");
pMethod.addIf(mappedKey, " == null");
pMethod.addThrowNew(IllegalStateException.class,
JavaSource.getQuoted("Unknown reference: "), " + ",
referencedKey);
pMethod.addEndIf();
int valNum = 0;
for (Iterator iter = referencedTable.getTable().getPrimaryKey().getColumns(); iter.hasNext(); ) {
Column referencedKeyColumn = (Column) iter.next();
Column localColumn = null;
for (Iterator iter2 = foreignKey.getColumnLinks(); iter2.hasNext(); ) {
ForeignKey.ColumnLink link = (ForeignKey.ColumnLink) iter2.next();
if (link.getReferencedColumn().equals(referencedKeyColumn)) {
localColumn = link.getLocalColumn();
break;
}
}
if (localColumn == null) {
throw new IllegalStateException("Unable to find the column referencing " + referencedKeyColumn.getQName());
}
int colNum = -1, i = 0;
for (Iterator iter2 = pTableInfo.getTable().getColumns(); iter2.hasNext(); ) {
Column col = (Column) iter2.next();
if (col.equals(localColumn)) {
colNum = i;
break;
}
++i;
}
if (colNum == -1) {
throw new IllegalStateException("Unable to find the column " + localColumn.getQName() + " in table " + pTableInfo.getTable().getQName());
}
pMethod.addLine(pRow, "[" + colNum + "] = ", mappedKey, ".getValues()[" + valNum++ + "];");
}
}
}
private List tablesByOrder = new ArrayList();
private Map tablesByName = new HashMap();
private Set propertyNames = new HashSet();
private boolean generatingLogging;
private boolean isReferencing(Table pReferencingTable, Table pReferencedTable) {
for (Iterator iter = pReferencingTable.getForeignKeys(); iter.hasNext(); ) {
ForeignKey foreignKey = (ForeignKey) iter.next();
if (foreignKey.getReferencedTable().equals(pReferencedTable)) {
return true;
}
}
return false;
}
/** <p>Returns whether the generator is creating logging statements. By default
* no logging statements are created.</p>
* <p>The default
* implementation creates logging statements suitable for the JaxMe logging
* package. To change this, create a subclass and overwrite the following
* methods: {@link #logEntering(JavaMethod, Object)},
* {@link #logExiting(JavaMethod, Object)}, {@link #logFinest(JavaMethod, Object, Object)},
* {@link #logFinestEntering(JavaMethod, Object)}, and
* {@link #logFinestExiting(JavaMethod, Object)}.</p>
* @see #setGeneratingLogging(boolean)
*/
public boolean isGeneratingLogging() {
return generatingLogging;
}
/** <p>Sets whether the generator is creating logging statements. By default
* no logging statements are created.</p>
* <p>The default
* implementation creates logging statements suitable for the JaxMe logging
* package. To change this, create a subclass and overwrite the following
* methods: {@link #logEntering(JavaMethod, Object)},
* {@link #logExiting(JavaMethod, Object)}, {@link #logFinest(JavaMethod, Object, Object)},
* {@link #logFinestEntering(JavaMethod, Object)}, and
* {@link #logFinestExiting(JavaMethod, Object)}.</p>
* @see #isGeneratingLogging()
*/
public void setGeneratingLogging(boolean pGeneratingLogging) {
generatingLogging = pGeneratingLogging;
}
/** <p>Creates the code for initialization of the logging framework.
* The default implementation generates code creating an instance of
* {@link org.apache.ws.jaxme.logging.Logger}.</p>
*/
protected void initLogging(JavaSource pSource) {
if (isGeneratingLogging()) {
JavaField jf = pSource.newJavaField("logger", Logger.class, JavaSource.PRIVATE);
jf.setFinal(true);
jf.setStatic(true);
jf.addLine(LoggerAccess.class, ".getLogger(", pSource.getQName(), ".class)");
}
}
/** <p>Creates code for logging the entrance into a method with
* fine level.</p>
* <p><em>Note:</em> The method should consider the {@link #isGeneratingLogging()}
* value.</p>
*
* @param pMethod The method in which a logging statement should be inserted
* @param pValues An array of additional values, possibly null
*/
protected void logEntering(JavaMethod pMethod, Object pValues) {
if (isGeneratingLogging()) {
LocalJavaField mName = pMethod.newJavaField(String.class, "mName");
mName.addLine(JavaSource.getQuoted(pMethod.getLoggingSignature()));
mName.setFinal(true);
if (pValues == null) {
pMethod.addLine("logger.entering(", mName, ");");
} else {
pMethod.addLine("logger.entering(", mName, ", ", pValues, ");");
}
}
}
/** <p>Creates code for logging the entrance into a method with
* finest level.</p>
* <p><em>Note:</em> The method should consider the {@link #isGeneratingLogging()}
* value.</p>
* <p><em>Implementation note:</em> The default implementation is
* equivalent to <code>logFinest(pMethod, "->", pValues)</code>.</p>
*
* @param pMethod The method in which a logging statement should be inserted
* @param pValues An array of additional values, possibly null
*/
protected void logFinestEntering(JavaMethod pMethod, Object pValues) {
if (isGeneratingLogging()) {
LocalJavaField mName = pMethod.newJavaField(String.class, "mName");
mName.addLine(JavaSource.getQuoted(pMethod.getLoggingSignature()));
mName.setFinal(true);
logFinest(pMethod, JavaSource.getQuoted("->"), pValues);
}
}
/** <p>Creates code for logging the exit from a method with
* fine level.</p>
* <p><em>Note:</em> The method should consider the {@link #isGeneratingLogging()}
* value.</p>
*
* @param pMethod The method in which a logging statement should be inserted
* @param pValues An array of additional values, possibly null
*/
protected void logExiting(JavaMethod pMethod, Object pValues) {
if (isGeneratingLogging()) {
if (pValues == null) {
pMethod.addLine("logger.exiting(mName);");
} else {
pMethod.addLine("logger.exiting(mName, ", pValues, ");");
}
}
}
/** <p>Creates code for logging the exit from a method with
* fine level.</p>
* <p><em>Note:</em> The method should consider the {@link #isGeneratingLogging()}
* value.</p>
* <p><em>Implementation note:</em> The default implementation is
* equivalent to <code>logFinest(pMethod, "<-", pValues)</code>.</p>
*
* @param pMethod The method in which a logging statement should be inserted
* @param pValues An array of additional values, possibly null
*/
protected void logFinestExiting(JavaMethod pMethod, Object pValues) {
logFinest(pMethod, JavaSource.getQuoted("->"), pValues);
}
/** <p>Creates code for logging a message with finest level.</p>
* <p><em>Note:</em> The method should consider the {@link #isGeneratingLogging()}
* value.</p>
*
* @param pMethod The method in which a logging statement should be inserted
* @param pMsg The message being logged
* @param pValues An array of additional values, possibly null
*/
protected void logFinest(JavaMethod pMethod, Object pMsg, Object pValues) {
if (isGeneratingLogging()) {
if (pValues == null) {
pMethod.addLine("logger.finest(mName, ", pMsg, ");");
} else {
pMethod.addLine("logger.finest(mName, ", pMsg, ", ", pValues, ");");
}
}
}
/** Adds a new table to the list of tables. The table must not
* contain a forward reference. Additionally, the table must not
* be referenced by any other table, which has already been added
* to the list.</p>
* @param pTable The table being cloned
* @param pUpdater The column updater to use for changing the
* updated columns.
*/
public void addTable(Table pTable, ColumnUpdater pUpdater) {
String name = pTable.getQName();
if (tablesByName.containsKey(pTable)) {
throw new IllegalStateException("A table " + name + " has already been added.");
}
if (isReferencing(pTable, pTable)) {
throw new IllegalStateException("The table " + name + " is containing self references.");
}
for (Iterator iter = tablesByOrder.iterator(); iter.hasNext(); ) {
TableInfo tableInfo = (TableInfo) iter.next();
if (isReferencing(tableInfo.getTable(), pTable)) {
throw new IllegalStateException("The table " + tableInfo.getTable().getQName() +
" contains a forward reference to the table " + name + ".");
}
}
String s = pTable.getName().getName();
String t = s;
int num = 0;
for (;;) {
t = Character.toUpperCase(t.charAt(0)) + t.substring(1);
if (!propertyNames.contains(t)) {
break;
}
t = s + num++;
}
propertyNames.add(t);
TableInfo tableInfo = new TableInfo(pTable, t);
tableInfo.setPrimaryKey(pTable.getPrimaryKey() != null);
if (pUpdater != null) {
tableInfo.add(pUpdater);
}
for (Iterator iter = pTable.getForeignKeys(); iter.hasNext(); ) {
ForeignKey foreignKey = (ForeignKey) iter.next();
Table referencedTable = foreignKey.getReferencedTable();
for (Iterator iter2 = tablesByOrder.iterator(); iter2.hasNext(); ) {
TableInfo referencedTableInfo = (TableInfo) iter2.next();
if (referencedTableInfo.getTable().equals(referencedTable)) {
ColumnUpdater columnUpdater = new ForeignKeyUpdater(foreignKey, referencedTableInfo);
tableInfo.add(columnUpdater);
if (!referencedTableInfo.hasPrimaryKey()) {
throw new IllegalStateException("The table " + pTable.getQName() +
" is referencing table " + referencedTable.getQName() +
", which doesn't have a primary key.");
}
referencedTableInfo.setReferenced(true);
}
}
}
tablesByName.put(name, tableInfo);
tablesByOrder.add(tableInfo);
}
/** <p>Returns the name of the inner class CacheData.</p>
*/
protected JavaQName getCacheDataClassName(JavaQName pQName) {
return JavaQNameImpl.getInnerInstance(pQName, "CacheData");
}
/** <p>Generates the innner class CacheData.</p>
*/
protected JavaInnerClass getCacheDataClass(JavaSource pSource) {
JavaInnerClass jic = pSource.newJavaInnerClass("CacheData", JavaSource.PRIVATE);
JavaField name = jic.newJavaField("name", String.class, JavaSource.PRIVATE);
name.setFinal(true);
JavaField values = jic.newJavaField("values", Object[].class, JavaSource.PRIVATE);
values.setFinal(true);
JavaConstructor jcon = jic.newJavaConstructor(JavaSource.PRIVATE);
DirectAccessible pName = jcon.addParam(String.class, "pName");
DirectAccessible pValues = jcon.addParam(Object[].class, "pValues");
jcon.addLine(name, " = ", pName, ";");
jcon.addLine(values, " = ", pValues, ";");
JavaMethod getNameMethod = jic.newJavaMethod("getName", String.class, JavaSource.PUBLIC);
getNameMethod.addLine("return ", name, ";");
JavaMethod getValuesMethod = jic.newJavaMethod("getValues", Object[].class, JavaSource.PUBLIC);
getValuesMethod.addLine("return ", values, ";");
{
JavaMethod jm = jic.newJavaMethod("toString", String.class, JavaSource.PUBLIC);
LocalJavaField sb = jm.newJavaField(StringBuffer.class, "sb");
sb.addLine("new ", StringBuffer.class, "(", name, ")");
DirectAccessible loopVar = jm.addForArray(values);
jm.addLine(sb, ".append(", JavaSource.getQuoted(", "), ").append(",
values, "[", loopVar, "]);");
jm.addEndFor();
jm.addLine("return ", sb, ".toString();");
}
{
JavaMethod jm = jic.newJavaMethod("hashCode", int.class, JavaSource.PUBLIC);
LocalJavaField hashCodeResult = jm.newJavaField(int.class, "result");
hashCodeResult.addLine(name, ".hashCode() + ", values, ".length;");
DirectAccessible loopVar = jm.addForArray(values);
LocalJavaField o = jm.newJavaField(Object.class, "o");
o.addLine(values, "[", loopVar, "]");
jm.addIf(o, " != null");
jm.addLine(hashCodeResult, " += ", o, ".hashCode();");
jm.addEndIf();
jm.addEndFor();
jm.addLine("return ", hashCodeResult, ";");
}
{
JavaMethod jm = jic.newJavaMethod("equals", boolean.class, JavaSource.PUBLIC);
DirectAccessible o = jm.addParam(Object.class, "o");
jm.addIf(o, " == null || !(", o, " instanceof ", jic.getQName(), ")");
jm.addLine("return false;");
jm.addEndIf();
LocalJavaField other = jm.newJavaField(jic.getQName(), "other");
other.addLine("(", jic.getQName(), ") ", o);
jm.addIf("!", name, ".equals(", other, ".name) || ", values, ".length != ",
other, ".values.length");
jm.addLine("return false;");
jm.addEndIf();
DirectAccessible loopVar = jm.addForArray(values);
LocalJavaField v = jm.newJavaField(Object.class, "v");
v.addLine(values, "[", loopVar, "]");
jm.addIf(v, " == null");
jm.addIf(other, ".values[", loopVar, "] != null");
jm.addLine("return false;");
jm.addEndIf();
jm.addElse();
jm.addIf("!", v, ".equals(", other, ".values[", loopVar, "])");
jm.addLine("return false;");
jm.addEndIf();
jm.addEndIf();
jm.addEndFor();
jm.addLine("return true;");
}
return jic;
}
/** <p>Generates code for reading a ResultSet's column.</p>
*/
protected void setResultSetValue(JavaMethod pMethod, Column pColumn,
DirectAccessible pResultSet,
int pColumnNum, Object pTarget) {
Integer p = new Integer(pColumnNum+1);
Column.Type type = pColumn.getType();
if (Column.Type.BIGINT.equals(type)) {
LocalJavaField l = pMethod.newJavaField(long.class);
l.addLine(pResultSet, ".getLong(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Long.class, "(", l, ");");
pMethod.addEndIf();
} else if (pColumn.isBinaryColumn()) {
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ",
pResultSet, ".getBytes(", p, ");");
} else if (Column.Type.BIT.equals(type)) {
LocalJavaField b = pMethod.newJavaField(boolean.class);
b.addLine(pResultSet, ".getBoolean(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ", b,
" ? ", Boolean.class, ".TRUE : ", Boolean.class, ".FALSE;");
pMethod.addEndIf();
} else if (pColumn.isStringColumn()) {
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ",
pResultSet, ".getString(", p, ");");
} else if (Column.Type.DATE.equals(type)) {
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ",
pResultSet, ".getDate(", p, ");");
} else if (Column.Type.DOUBLE.equals(type)) {
LocalJavaField d = pMethod.newJavaField(double.class);
d.addLine(pResultSet, ".getDouble(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Double.class, "(", d, ");");
pMethod.addEndIf();
} else if (Column.Type.FLOAT.equals(type)) {
LocalJavaField f = pMethod.newJavaField(float.class);
f.addLine(pResultSet, ".getFloat(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Float.class, "(", f, ");");
pMethod.addEndIf();
} else if (Column.Type.INTEGER.equals(type)) {
LocalJavaField i = pMethod.newJavaField(int.class);
i.addLine(pResultSet, ".getInt(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Integer.class, "(", i, ");");
pMethod.addEndIf();
} else if (Column.Type.SMALLINT.equals(type)) {
LocalJavaField s = pMethod.newJavaField(short.class);
s.addLine(pResultSet, ".getShort(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Short.class, "(", s, ");");
pMethod.addEndIf();
} else if (Column.Type.TIME.equals(type)) {
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ",
pResultSet, ".getTime(", p, ");");
} else if (Column.Type.TIMESTAMP.equals(type)) {
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = ",
pResultSet, ".getTimestamp(", p, ");");
} else if (Column.Type.TINYINT.equals(type)) {
LocalJavaField b = pMethod.newJavaField(short.class);
b.addLine(pResultSet, ".getByte(", p, ");");
pMethod.addIf("!", pResultSet, ".wasNull()");
pMethod.addLine(pTarget, "[", Integer.toString(pColumnNum), "] = new ",
Byte.class, "(", b, ");");
pMethod.addEndIf();
} else {
throw new IllegalStateException("Unknown column type: " + type);
}
}
/** <p>Generates code for setting a PreparedStatement's parameter.</p>
*/
protected void setPreparedStatementValue(JavaMethod pMethod, Column pColumn,
Object pStmt, Object pParamNum,
Object pValue) {
if (!(pValue instanceof DirectAccessible)) {
LocalJavaField v = pMethod.newJavaField(Object.class);
v.addLine(pValue);
pValue = v;
}
Column.Type type = pColumn.getType();
pMethod.addIf(pValue, " == null");
pMethod.addLine(pStmt, ".setNull(", pParamNum, ", ", Types.class, ".",
type, ");");
pMethod.addElse();
if (Column.Type.BIGINT.equals(type)) {
pMethod.addLine(pStmt, ".setLong(", pParamNum, ", ((", Long.class,
") ", pValue, ").longValue());");
} else if (pColumn.isBinaryColumn()) {
pMethod.addLine(pStmt, ".setBytes(", pParamNum, ", (", byte[].class,
") ", pValue, ");");
} else if (Column.Type.BIT.equals(type)) {
pMethod.addLine(pStmt, ".setBoolean(", pParamNum, ", ((", Boolean.class,
") ", pValue, ").booleanValue());");
} else if (pColumn.isStringColumn()) {
pMethod.addLine(pStmt, ".setString(", pParamNum, ", (", String.class,
") ", pValue, ");");
} else if (Column.Type.DATE.equals(type)) {
pMethod.addLine(pStmt, ".setDate(", pParamNum, ", (", Date.class,
") ", pValue, ");");
} else if (Column.Type.DOUBLE.equals(type)) {
pMethod.addLine(pStmt, ".setDouble(", pParamNum, ", ((", Double.class,
") ", pValue, ").doubleValue());");
} else if (Column.Type.FLOAT.equals(type)) {
pMethod.addLine(pStmt, ".setFloat(", pParamNum, ", ((", Float.class,
") ", pValue, ").floatValue());");
} else if (Column.Type.INTEGER.equals(type)) {
pMethod.addLine(pStmt, ".setInt(", pParamNum, ", ((", Integer.class,
") ", pValue, ").intValue());");
} else if (Column.Type.SMALLINT.equals(type)) {
pMethod.addLine(pStmt, ".setShort(", pParamNum, ", ((", Short.class,
") ", pValue, ").shortValue());");
} else if (Column.Type.TIME.equals(type)) {
pMethod.addLine(pStmt, ".setTime(", pParamNum, ", (", Time.class,
") ", pValue, ");");
} else if (Column.Type.TIMESTAMP.equals(type)) {
pMethod.addLine(pStmt, ".setTimestamp(", pParamNum, ", (", Timestamp.class,
") ", pValue, ");");
} else if (Column.Type.TINYINT.equals(type)) {
pMethod.addLine(pStmt, ".setByte(", pParamNum, ", ((", Byte.class, ") ",
pValue, ").byteValue());");
} else {
throw new IllegalStateException("Unknown column type: " + type);
}
pMethod.addEndIf();
}
/** <p>Generates code for reading all rows matching the given key.</p>
*/
protected void getSelectRowsCode(JavaMethod pMethod,
TableInfo pTableInfo,
ColumnSet pColumnSet,
DirectAccessible pConn,
DirectAccessible pMap,
DirectAccessible pValues,
boolean pReturnValue) {
Table table = pTableInfo.getTable();
SelectStatement statement = table.getSelectStatement();
statement.getWhere().addColumnSetQuery(pColumnSet, statement.getTableReference());
String s = table.getSchema().getSQLFactory().newSQLGenerator().getQuery(statement);
Object query = JavaSource.getQuoted(s);
if (isGeneratingLogging()) {
LocalJavaField q = pMethod.newJavaField(String.class);
q.addLine(query);
query = q;
logFinest(pMethod, query, pValues);
}
LocalJavaField stmt = pMethod.newJavaField(PreparedStatement.class);
stmt.addLine(pConn, ".prepareStatement(", JavaSource.getQuoted(s), ");");
LocalJavaField isStmtClosed = pMethod.newJavaField(boolean.class);
isStmtClosed.addLine("false");
pMethod.addTry();
int paramNum = 0;
for (Iterator iter = pColumnSet.getColumns(); iter.hasNext(); ) {
Object v = new Object[]{pValues, "[", Integer.toString(paramNum), "]"};
Column column = (Column) iter.next();
setPreparedStatementValue(pMethod, column, stmt, Integer.toString(++paramNum), v);
}
LocalJavaField rs = pMethod.newJavaField(ResultSet.class, "rs");
rs.addLine(stmt, ".executeQuery()");
LocalJavaField isRsClosed = pMethod.newJavaField(boolean.class);
isRsClosed.addLine("false");
LocalJavaField result;
if (pReturnValue) {
result = pMethod.newJavaField(Object[].class, "result");
result.addLine("null");
} else {
result = null;
}
pMethod.addTry();
pMethod.addWhile(rs, ".next()");
if (result != null) {
pMethod.addIf(result, " != null");
pMethod.addThrowNew(IllegalStateException.class,
JavaSource.getQuoted("Expected a single row only."));
pMethod.addEndIf();
}
int resultColumnSize = 0;
for (Iterator iter = table.getColumns(); iter.hasNext(); iter.next()) {
++resultColumnSize;
}
LocalJavaField row = pMethod.newJavaField(Object[].class, "row");
row.addLine("new ", Object.class, "[" + resultColumnSize + "];");
int resultColumnNum = 0;
for (Iterator iter = table.getColumns(); iter.hasNext(); ) {
Column column = (Column) iter.next();
setResultSetValue(pMethod, column, rs, resultColumnNum++, row);
}
pMethod.addLine(((result == null) ? "" : "result = "),
getInsertRowMethodName(pTableInfo), "(", pConn, ", ", pMap, ", ",
row, ");");
pMethod.addEndWhile();
pMethod.addLine(isRsClosed, " = true;");
pMethod.addLine(rs, ".close();");
pMethod.addFinally();
pMethod.addIf("!", isRsClosed);
pMethod.addTry();
pMethod.addLine(rs, ".close();");
pMethod.addCatch(Throwable.class, "pIgnore");
pMethod.addEndTry();
pMethod.addEndIf();
pMethod.addEndTry();
pMethod.addLine(isStmtClosed, " = true;");
pMethod.addLine(stmt, ".close();");
if (result != null) {
logExiting(pMethod, result);
pMethod.addLine("return ", result, ";");
} else {
logFinestExiting(pMethod, null);
}
pMethod.addFinally();
pMethod.addIf("!", isStmtClosed);
pMethod.addTry();
pMethod.addLine(stmt, ".close();");
pMethod.addCatch(Throwable.class, "pIgnore");
pMethod.addEndTry();
pMethod.addEndIf();
pMethod.addEndTry();
}
/** <p>Returns the name of the method for cloning one row from the
* given table.</p>
*/
protected String getInsertRowMethodName(TableInfo pTableInfo) {
return "clone" + pTableInfo.getPropertyName() + "Row";
}
/** <p>Creates an instance of the inner class CacheData by reading
* the key from the given row.</p>
*/
protected LocalJavaField getCacheDataClassInstance(JavaMethod pMethod,
TableInfo pTableInfo,
ColumnSet pColumnSet,
DirectAccessible pValues) {
Table table = pColumnSet.getTable();
List params = new ArrayList();
for (Iterator iter = pColumnSet.getColumns(); iter.hasNext(); ) {
Column primaryKeyColumn = (Column) iter.next();
int index = -1;
int i = 0;
for (Iterator iter2 = table.getColumns(); iter2.hasNext(); ++i) {
Column tableColumn = (Column) iter2.next();
if (tableColumn.equals(primaryKeyColumn)) {
index = i;
break;
}
}
if (index == -1) {
throw new IllegalStateException("Key column " + primaryKeyColumn.getQName() + " not found.");
}
if (params.size() > 0) {
params.add(", ");
}
params.add(new Object[]{pValues, "[" + index + "]"});
}
JavaQName cacheDataClass = getCacheDataClassName(pMethod.getJavaSource().getQName());
LocalJavaField jf = pMethod.newJavaField(cacheDataClass);
jf.addLine("new ", cacheDataClass, "(",
JavaSource.getQuoted(pTableInfo.getPropertyName()),
", new ", Object[].class, "{", params, "})");
return jf;
}
/** <p>Updates a row by reading the values from an instance of the inner
* class CacheData.</p>
*/
protected void getApplyCacheData(JavaMethod pMethod,
TableInfo pTableInfo,
ColumnSet pColumnSet,
DirectAccessible pRow,
DirectAccessible pData) {
Table table = pTableInfo.getTable();
for (Iterator iter = pColumnSet.getColumns(); iter.hasNext(); ) {
Column primaryKeyColumn = (Column) iter.next();
int index = -1;
int i = 0;
for (Iterator iter2 = table.getColumns(); iter2.hasNext(); ++i) {
Column tableColumn = (Column) iter2.next();
if (tableColumn.equals(primaryKeyColumn)) {
index = i;
break;
}
}
if (index == -1) {
throw new IllegalStateException("Key column " + primaryKeyColumn.getQName() + " not found.");
}
pMethod.addLine(pRow, "[" + index + "] = ", pData, "[" + (i+1) + "];");
}
}
/** <p>Creates a method for cloning one row from the given table.</p>
*/
protected JavaMethod getInsertRowMethod(JavaSource pSource, TableInfo pTableInfo) {
Table table = pTableInfo.getTable();
JavaMethod jm = pSource.newJavaMethod(getInsertRowMethodName(pTableInfo),
Object[].class, JavaSource.PRIVATE);
jm.addThrows(SQLException.class);
DirectAccessible connection = jm.addParam(Connection.class, "pConn");
DirectAccessible map = jm.addParam(Map.class, "pMap");
DirectAccessible values = jm.addParam(Object[].class, "pValue");
logFinestEntering(jm, values);
LocalJavaField baseKey = null;
if (table.getPrimaryKey() != null) {
baseKey = getCacheDataClassInstance(jm, pTableInfo,
table.getPrimaryKey(), values);
jm.addIf(map, ".containsKey(", baseKey, ")");
logFinestExiting(jm, JavaSource.getQuoted("null (Already cloned)"));
jm.addLine("return null;");
jm.addEndIf();
}
for (Iterator iter = pTableInfo.getColumnUpdaters(); iter.hasNext(); ) {
((ColumnUpdater) iter.next()).update(jm, pTableInfo, connection, map, values);
}
jm.addLine(jm.getName(), "(", connection, ", ", values, ");");
if (baseKey != null) {
LocalJavaField clonedKey = getCacheDataClassInstance(jm, pTableInfo,
table.getPrimaryKey(),
values);
jm.addLine(map, ".put(", baseKey, ", ", clonedKey, ");");
}
// Clone objects referencing this object
LocalJavaField referencedValues = null;
for (Iterator referencingIter = tablesByOrder.iterator(); referencingIter.hasNext(); ) {
TableInfo prevTableInfo = (TableInfo) referencingIter.next();
Table prevTable = prevTableInfo.getTable();
for (Iterator fkIter = prevTable.getForeignKeys(); fkIter.hasNext(); ) {
ForeignKey fk = (ForeignKey) fkIter.next();
if (fk.getReferencedTable().equals(table)) {
if (referencedValues == null) {
referencedValues = jm.newJavaField(Object[].class);
referencedValues.addLine(baseKey, ".getValues()");
}
getSelectRowsCode(jm, prevTableInfo, fk, connection, map, referencedValues, false);
}
}
}
logFinestExiting(jm, values);
jm.addLine("return ", values, ";");
return jm;
}
/** <p>Creates a method for cloning one row from the given table.</p>
*/
protected JavaMethod getInnerInsertRowMethod(JavaSource pSource, TableInfo pTableInfo) {
Table table = pTableInfo.getTable();
JavaMethod jm = pSource.newJavaMethod(getInsertRowMethodName(pTableInfo),
JavaQNameImpl.VOID, JavaSource.PRIVATE);
jm.addThrows(SQLException.class);
DirectAccessible connection = jm.addParam(Connection.class, "pConn");
DirectAccessible values = jm.addParam(Object[].class, "pValue");
logFinestEntering(jm, null);
InsertStatement insertStatement = table.getInsertStatement();
String s = table.getSchema().getSQLFactory().newSQLGenerator().getQuery(insertStatement);
Object query = JavaSource.getQuoted(s);
if (isGeneratingLogging()) {
LocalJavaField q = jm.newJavaField(String.class);
q.addLine(query);
query = q;
logFinest(jm, query, values);
}
LocalJavaField stmt = jm.newJavaField(PreparedStatement.class, "stmt");
stmt.setFinal(true);
stmt.addLine(connection, ".prepareStatement(", query, ");");
LocalJavaField isStmtClosed = jm.newJavaField(boolean.class, "isStmtClosed");
isStmtClosed.addLine("false");
jm.addTry();
int paramNum = 0;
for (Iterator iter = table.getColumns(); iter.hasNext(); ) {
Column column = (Column) iter.next();
Object v = new Object[]{ values, "[", Integer.toString(paramNum), "]" };
setPreparedStatementValue(jm, column, stmt, Integer.toString(++paramNum), v);
}
jm.addLine(stmt, ".executeUpdate();");
jm.addLine(isStmtClosed, " = true;");
jm.addLine(stmt, ".close();");
jm.addFinally();
jm.addIf("!", isStmtClosed);
jm.addTry();
jm.addLine(stmt, ".close();");
jm.addCatch(Throwable.class, "ignore");
jm.addEndTry();
jm.addEndIf();
jm.addEndTry();
logFinestExiting(jm, null);
return jm;
}
/** <p>Actually creates the public "clone" method.</p>
*/
protected JavaMethod getPublicCloneMethod(JavaSource pSource) {
TableInfo headTable;
{
Iterator iter = tablesByOrder.iterator();
if (!iter.hasNext()) {
throw new IllegalStateException("No tables have been added.");
}
headTable = (TableInfo) iter.next();
}
JavaQName resultType = JavaQNameImpl.getInstance(Object[].class);
JavaMethod jm = pSource.newJavaMethod("clone", resultType, JavaSource.PUBLIC);
jm.addThrows(SQLException.class);
JavaComment jc = jm.newComment();
jc.addLine("<p>This method takes as input the key values of a row in the table " +
headTable.getTable().getQName() + ".");
jc.addLine("The key values are given by the array <code>pRow</code>:");
jc.addLine("<ul>");
int i = 0;
for (Iterator iter = headTable.getTable().getPrimaryKey().getColumns();
iter.hasNext(); i++) {
Column col = (Column) iter.next();
jc.addLine(" <li><code>pRow[" + i+ "] = " + col.getQName() + "</code></li>");
}
jc.addLine("</ul>");
jc.addLine("The method updates the rows version number and creates a new row");
jc.addLine("with the updated values.</p>");
{
Iterator iter = tablesByOrder.iterator();
iter.next();
if (iter.hasNext()) {
jc.addLine("<p>Once the new row is created, the method searches for entries");
jc.addLine("referencing the old row in the following tables:");
jc.addLine("<table>");
do {
TableInfo subTable = (TableInfo) iter.next();
jc.addLine(" <tr><td>" + subTable.getTable().getQName() + "</td></tr>");
} while (iter.hasNext());
jc.addLine("All the referencing entries are cloned as well, with updated");
jc.addLine("references.");
}
}
DirectAccessible conn = jm.addParam(Connection.class, "pConn");
DirectAccessible row = jm.addParam(resultType, "pRow");
logEntering(jm, new Object[]{"new ", Object[].class, "{", conn, ", ", row, "}"});
LocalJavaField map = jm.newJavaField(Map.class, "clonedObjects");
map.addLine("new ", HashMap.class, "()");
getSelectRowsCode(jm, headTable, headTable.getTable().getPrimaryKey(),
conn, map, row, true);
return jm;
}
/** <p>Creates a method for updating one row in the head table.</p>
*/
public JavaMethod getCloneMethod(JavaSource pSource) {
initLogging(pSource);
for (Iterator iter = tablesByOrder.iterator(); iter.hasNext(); ) {
TableInfo tableInfo = (TableInfo) iter.next();
getInsertRowMethod(pSource, tableInfo);
getInnerInsertRowMethod(pSource, tableInfo);
}
getCacheDataClass(pSource);
getPublicCloneMethod(pSource);
return null;
}
}