/*
* Copyright 2013 cruxframework.org.
*
* 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 org.cruxframework.crux.core.rebind.database.sql;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import org.cruxframework.crux.core.client.collection.Array;
import org.cruxframework.crux.core.client.db.Cursor;
import org.cruxframework.crux.core.client.db.Cursor.CursorDirection;
import org.cruxframework.crux.core.client.db.DatabaseCursorCallback;
import org.cruxframework.crux.core.client.db.DatabaseDeleteCallback;
import org.cruxframework.crux.core.client.db.DatabaseRetrieveCallback;
import org.cruxframework.crux.core.client.db.DatabaseWriteCallback;
import org.cruxframework.crux.core.client.db.Index;
import org.cruxframework.crux.core.client.db.KeyRange;
import org.cruxframework.crux.core.client.db.KeyRangeFactory;
import org.cruxframework.crux.core.client.db.WSQLAbstractDatabase;
import org.cruxframework.crux.core.client.db.WSQLAbstractObjectStore;
import org.cruxframework.crux.core.client.db.WSQLTransaction;
import org.cruxframework.crux.core.client.utils.EscapeUtils;
import org.cruxframework.crux.core.client.utils.StringUtils;
import org.cruxframework.crux.core.rebind.CruxGeneratorException;
import org.cruxframework.crux.core.rebind.context.RebindContext;
import org.cruxframework.crux.core.rebind.database.AbstractDatabaseProxyCreator.IndexData;
import org.cruxframework.crux.core.utils.JClassUtils;
import com.google.gwt.core.client.JsArrayMixed;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class SQLObjectStoreProxyCreator extends SQLAbstractKeyValueProxyCreator
{
private JClassType abstractObjectStoreType;
private String dbVariable;
private final Set<IndexData> indexes;
private final boolean autoIncrement;
public SQLObjectStoreProxyCreator(RebindContext context, JClassType targetObjectType, String objectStoreName,
String[] keyPath, boolean autoIncrement, Set<IndexData> indexes)
{
super(context, targetObjectType, objectStoreName, keyPath);
this.autoIncrement = autoIncrement;
validatePrimaryKeyPath(targetObjectType, objectStoreName, keyPath);
this.indexes = indexes;
this.abstractObjectStoreType = context.getGeneratorContext().getTypeOracle().findType(WSQLAbstractObjectStore.class.getCanonicalName());
this.dbVariable = "db";
}
@Override
protected void generateProxyContructor(SourcePrinter srcWriter) throws CruxGeneratorException
{
srcWriter.println("public "+getProxySimpleName()+"(WSQLAbstractDatabase db, String name, WSQLTransaction transaction){");
srcWriter.println("super(db, name, transaction);");
srcWriter.println("}");
}
@Override
protected void generateProxyMethods(SourcePrinter srcWriter) throws CruxGeneratorException
{
generateGetNativeKeyMethod(srcWriter);
generateGetIndexedColumnNamesMethod(srcWriter, getIndexColumns());
generateGetKeyPathMethod(srcWriter);
generateGetObjectStoreNameMethod(srcWriter);
generateAddKeyRangeToQueryMethod(srcWriter);
generateAddKeyToQueryMethod(srcWriter);
generateDeriveKeyMethod(srcWriter);
generateSetObjectKeyMethod(srcWriter);
generateDecodeObjectMethod(srcWriter);
generateEncodeObjectMethod(srcWriter);
generateGetCreateTableSQLMethod(srcWriter);
generateGetIndexNamesMethod(srcWriter);
generateIsAutoIncrementMethod(srcWriter);
generateOpenCursorMethod(srcWriter);
generateGetIndexMethod(srcWriter);
generateGetKeyRangeFactoryMethod(srcWriter, objectStoreName);
}
protected Set<String> getIndexColumns()
{
Set<String> indexColumns = new HashSet<String>();
for (IndexData indexData:indexes)
{
for (String col : indexData.keyPath)
{
indexColumns.add(col);
}
}
return indexColumns;
}
protected void generateGetObjectStoreNameMethod(SourcePrinter srcWriter)
{
srcWriter.println("public String getObjectStoreName(){");
srcWriter.println("return "+EscapeUtils.quote(objectStoreName)+";");
srcWriter.println("}");
srcWriter.println();
}
protected void generateDeriveKeyMethod(SourcePrinter srcWriter)
{
srcWriter.println("protected "+getKeyTypeName()+" getKey("+ getTargetObjectClassName()+" object){");
srcWriter.print("boolean hasKey = ");
boolean first = true;
for (String k : keyPath)
{
if (!first)
{
srcWriter.print(" || ");
}
String getterMethod = JClassUtils.getGetterMethod(k, targetObjectType);
srcWriter.print("object."+getterMethod+"() != null");
first = false;
}
srcWriter.println(";");
srcWriter.println("if (hasKey){");
srcWriter.print(getKeyTypeName()+" key");
if (hasCompositeKey())
{
srcWriter.println(" = new Object["+keyPath.length+"];");
int i = 0;
for (String k : keyPath)
{
String getterMethod = JClassUtils.getGetterMethod(k, targetObjectType);
srcWriter.print("key ["+i+"] = object."+getterMethod+"();");
i++;
}
}
else
{
srcWriter.println(" = object."+JClassUtils.getGetterMethod(keyPath[0], targetObjectType)+"();");
}
srcWriter.println("return key;");
if (autoIncrement)
{
if (!getKeyTypeName().equals("Integer"))
{
throw new CruxGeneratorException("Auto increment keys can only be used on integer keys");
}
srcWriter.println("} else {");
srcWriter.println("return null;");
}
else
{
srcWriter.println("} else {");
srcWriter.println("reportError(db.messages.objectStoreDeriveKeyError(name));");
srcWriter.println("return null;");
}
srcWriter.println("}");
srcWriter.println("}");
srcWriter.println();
}
protected void generateSetObjectKeyMethod(SourcePrinter srcWriter)
{
srcWriter.println("protected void setObjectKey("+getTargetObjectClassName()+" object, "+getKeyTypeName()+" key){");
if (hasCompositeKey())
{
for (int i = 0; i < keyPath.length; i++)
{
String k = keyPath[i];
JType jType = JClassUtils.getTypeForProperty(k, targetObjectType);
String setterMethod = JClassUtils.getSetterMethod(k, targetObjectType, jType);
if (setterMethod == null || setterMethod.isEmpty())
{
throw new CruxGeneratorException("There is no setter method for property ["+k+"] on type ["+targetObjectType.getQualifiedSourceName()+"]");
}
srcWriter.println("object."+setterMethod+"((key==null?null:("+jType.getParameterizedQualifiedSourceName()+")key["+i+"]));");
}
}
else
{
String k = keyPath[0];
JType jType = JClassUtils.getTypeForProperty(k, targetObjectType);
String setterMethod = JClassUtils.getSetterMethod(k, targetObjectType, jType);
if (setterMethod == null || setterMethod.isEmpty())
{
throw new CruxGeneratorException("There is no setter method for property ["+k+"] on type ["+targetObjectType.getQualifiedSourceName()+"]");
}
srcWriter.println("object."+setterMethod+"(key);");
}
srcWriter.println("}");
srcWriter.println();
}
protected void generateGetCreateTableSQLMethod(SourcePrinter srcWriter)
{
srcWriter.println("protected String getCreateTableSQL(){");
srcWriter.print("String sql = \"CREATE TABLE \\\"\"+name+\"\\\" (\\\"value\\\" BLOB");//TODO alterar nome da coluna pra evitar conflito com indices
Set<String> addedColumns = new HashSet<String>();
for (String col : keyPath)
{
if (!addedColumns.contains(col))
{
addedColumns.add(col);
JType propertyType = getPropertyType(col);
srcWriter.print(",\\\""+col+"\\\" "+getSQLTypeForProperty(propertyType)+" PRIMARY KEY");
if (autoIncrement)
{
if (!getKeyTypeName().equals("Integer"))
{
throw new CruxGeneratorException("Auto increment keys can only be used on integer keys");
}
srcWriter.print(" AUTOINCREMENT");
}
}
else
{
throw new CruxGeneratorException("Invalid KeyPath for object store ["+objectStoreName+"]. Duplicated column on keyPath ["+col+"]");
}
}
StringBuilder uniqueConstraints = new StringBuilder();
for (IndexData indexData: indexes)
{
if (indexData.unique)
{
uniqueConstraints.append(", UNIQUE(");
}
boolean firstCostraint = true;
for (String col : indexData.keyPath)
{
if (!addedColumns.contains(col))
{
addedColumns.add(col);
JType propertyType = getPropertyType(col);
srcWriter.print(",\\\""+col+"\\\" "+getSQLTypeForProperty(propertyType));
}
if (indexData.unique)
{
if (!firstCostraint)
{
uniqueConstraints.append(",");
}
uniqueConstraints.append("\\\""+col+"\\\"");
firstCostraint = false;
}
}
if (indexData.unique)
{
uniqueConstraints.append(") ON CONFLICT REPLACE");
}
}
srcWriter.print(uniqueConstraints.toString());
srcWriter.println(")\";");
srcWriter.println("return sql;");
srcWriter.println("}");
srcWriter.println();
}
protected void generateGetIndexNamesMethod(SourcePrinter srcWriter)
{
srcWriter.println("public String[] getIndexNames(){");
srcWriter.print("return new String[]{");
boolean first = true;
for (IndexData index : indexes)
{
if (!first)
{
srcWriter.print(",");
}
first = false;
srcWriter.print(EscapeUtils.quote(index.indexName));
}
srcWriter.println("};");
srcWriter.println("}");
srcWriter.println();
}
protected void generateIsAutoIncrementMethod(SourcePrinter srcWriter)
{
srcWriter.println("public boolean isAutoIncrement(){");
srcWriter.println("return "+autoIncrement+";");
srcWriter.println("}");
srcWriter.println();
}
protected void generateOpenCursorMethod(SourcePrinter srcWriter)
{
srcWriter.println("public void openCursor(KeyRange<"+getKeyTypeName()+"> keyRange, CursorDirection direction, final DatabaseCursorCallback<"+getKeyTypeName()+", "+getTargetObjectClassName()+"> callback){");
String cursorClassName = new SQLCursorProxyCreator(context, targetObjectType, objectStoreName, autoIncrement, getIndexColumns(), keyPath, keyPath, "ObjectStore_"+getTargetObjectClassName()).create();
srcWriter.println("new "+cursorClassName+"("+dbVariable+", (WSQLKeyRange<"+getKeyTypeName()+">)keyRange, direction, transaction).start(callback);");
srcWriter.println("}");
srcWriter.println();
}
private String getSQLTypeForProperty(JType jType)
{
if (jType.equals(stringType))
{
return "BLOB";
}
else if (jType.equals(integerType) || (jType.equals(JPrimitiveType.INT)))
{
return "INTEGER";
}
else if (jType.equals(doubleType) || (jType.equals(JPrimitiveType.DOUBLE)))
{
return "REAL";
}
else if (jType.equals(dateType))
{
return "INTEGER";
}
return "BLOB";
}
protected void generateGetIndexMethod(SourcePrinter srcWriter)
{
srcWriter.println("public <I> Index<"+getKeyTypeName()+", I, "+getTargetObjectClassName()+"> getIndex(String name){");
for(IndexData index: indexes)
{
srcWriter.println("if (StringUtils.unsafeEquals(name, "+EscapeUtils.quote(index.indexName)+")){");
String indexClassName = new SQLIndexProxyCreator(context, targetObjectType, objectStoreName, autoIncrement, index.keyPath, index.indexName, keyPath, getIndexColumns(), index.unique).create();
srcWriter.println("return (Index<"+getKeyTypeName()+", I, "+getTargetObjectClassName()+">) new " + indexClassName + "("+dbVariable+", transaction);");
srcWriter.println("}");
}
srcWriter.println("return null;");
srcWriter.println("}");
srcWriter.println();
}
@Override
public String getProxyQualifiedName()
{
return abstractObjectStoreType.getPackage().getName()+"."+getProxySimpleName();
}
@Override
public String getProxySimpleName()
{
String typeName = objectStoreName.replaceAll("\\W", "_");
return typeName+"_SQL_ObjectStore";
}
@Override
protected SourcePrinter getSourcePrinter()
{
String packageName = abstractObjectStoreType.getPackage().getName();
PrintWriter printWriter = context.getGeneratorContext().tryCreate(context.getLogger(), packageName, getProxySimpleName());
if (printWriter == null)
{
return null;
}
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, getProxySimpleName());
String[] imports = getImports();
for (String imp : imports)
{
composerFactory.addImport(imp);
}
composerFactory.setSuperclass("WSQLAbstractObjectStore<"+getKeyTypeName()+","+getTargetObjectClassName()+">");
return new SourceCodePrinter(composerFactory.createSourceWriter(context.getGeneratorContext(), printWriter), context.getLogger());
}
/**
* @return
*/
protected String[] getImports()
{
String[] imports = new String[] {
Array.class.getCanonicalName(),
WSQLAbstractDatabase.class.getCanonicalName(),
WSQLAbstractObjectStore.class.getCanonicalName(),
WSQLTransaction.class.getCanonicalName(),
DatabaseRetrieveCallback.class.getCanonicalName(),
DatabaseWriteCallback.class.getCanonicalName(),
JSONObject.class.getCanonicalName(),
DatabaseCursorCallback.class.getCanonicalName(),
KeyRangeFactory.class.getCanonicalName(),
Cursor.class.getCanonicalName(),
CursorDirection.class.getCanonicalName(),
StringUtils.class.getCanonicalName(),
DatabaseDeleteCallback.class.getCanonicalName(),
JsArrayMixed.class.getCanonicalName(),
Index.class.getCanonicalName(),
KeyRange.class.getCanonicalName()
};
return imports;
}
}