/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.query;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.servoy.base.query.BaseQueryTable;
import com.servoy.base.query.IBaseSQLCondition;
import com.servoy.j2db.util.serialize.IWriteReplace;
import com.servoy.j2db.util.serialize.ReplacedObject;
import com.servoy.j2db.util.visitor.DeepCloneVisitor;
import com.servoy.j2db.util.visitor.IVisitable;
import com.servoy.j2db.util.visitor.IVisitor;
import com.servoy.j2db.util.visitor.IVisitor.VistorResult;
import com.servoy.j2db.util.visitor.ReplaceVisitor;
/**
* Base class for all DML classes.
*
* @author rgansevles
*
*/
public abstract class AbstractBaseQuery implements ISQLQuery
{
private static final long serialVersionUID = 1L;
// This one is not final by choice! it has to touch this class first so that the classmapping hashmap is initialized
public static String QUERY_SERIALIZE_DOMAIN = "Q"; //$NON-NLS-1$
static
{
Map<Class< ? extends IWriteReplace>, Short> classMapping = new HashMap<Class< ? extends IWriteReplace>, Short>();
classMapping.put(AndCondition.class, Short.valueOf((short)1));
classMapping.put(BooleanCondition.class, Short.valueOf((short)2));
classMapping.put(ColumnType.class, Short.valueOf((short)3));
classMapping.put(CompareCondition.class, Short.valueOf((short)4));
classMapping.put(CustomCondition.class, Short.valueOf((short)5));
classMapping.put(OrCondition.class, Short.valueOf((short)6));
classMapping.put(Placeholder.class, Short.valueOf((short)7));
classMapping.put(TablePlaceholderKey.class, Short.valueOf((short)8));
classMapping.put(QueryAggregate.class, Short.valueOf((short)9));
classMapping.put(QueryColumn.class, Short.valueOf((short)10));
classMapping.put(QueryColumnValue.class, Short.valueOf((short)11));
classMapping.put(QueryCustomElement.class, Short.valueOf((short)12));
classMapping.put(QueryCustomJoin.class, Short.valueOf((short)13));
classMapping.put(QueryCustomSelect.class, Short.valueOf((short)14));
classMapping.put(QueryCustomSort.class, Short.valueOf((short)15));
classMapping.put(QueryCustomUpdate.class, Short.valueOf((short)16));
classMapping.put(QueryDelete.class, Short.valueOf((short)17));
classMapping.put(QueryFunction.class, Short.valueOf((short)18));
classMapping.put(QueryInsert.class, Short.valueOf((short)19));
classMapping.put(QueryJoin.class, Short.valueOf((short)20));
classMapping.put(QuerySelect.class, Short.valueOf((short)21));
classMapping.put(QuerySort.class, Short.valueOf((short)22));
classMapping.put(QueryTable1.class, Short.valueOf((short)-1)); // Legacy QueryData without dataSource, needed to deserialize old xml
classMapping.put(QueryTable.class, Short.valueOf((short)23));
classMapping.put(QueryUpdate.class, Short.valueOf((short)24));
classMapping.put(SetCondition.class, Short.valueOf((short)25));
classMapping.put(ExistsCondition.class, Short.valueOf((short)26));
classMapping.put(ObjectPlaceholderKey.class, Short.valueOf((short)27));
classMapping.put(QueryCompositeJoin.class, Short.valueOf((short)28));
ReplacedObject.installClassMapping(QUERY_SERIALIZE_DOMAIN, classMapping);
}
public static void initialize()
{
// this method triggers the static code above.
}
@Override
public Object shallowClone() throws CloneNotSupportedException
{
return super.clone();
}
public static <T> T acceptVisitor(T o, IVisitor visitor)
{
if (o == null)
{
return null;
}
Object v = visitor.visit(o);
T o2;
if (v instanceof VistorResult)
{
o2 = (T)((VistorResult)v).object;
if (!((VistorResult)v).continueTraversal)
{
return o2;
}
}
else
{
o2 = (T)v;
if (o2 != o)
{
// the visitor returned something else then the input, visting stops.
return o2;
}
}
if (o2 instanceof List)
{
for (int i = 0; i < ((List)o2).size(); i++)
{
((List<Object>)o2).set(i, acceptVisitor(((List)o2).get(i), visitor));
}
}
else if (o2 instanceof Map)
{
Iterator<Map.Entry> it = ((Map)o2).entrySet().iterator();
while (it.hasNext())
{
Map.Entry entry = it.next();
entry.setValue(acceptVisitor(entry.getValue(), visitor));
}
}
else if (o2 instanceof Object[])
{
for (int i = 0; i < ((Object[])o2).length; i++)
{
((Object[])o2)[i] = acceptVisitor(((Object[])o2)[i], visitor);
}
}
else if (o2 instanceof IVisitable)
{
((IVisitable)o2).acceptVisitor(visitor);
}
return o2;
}
public static <T> T deepClone(T o)
{
return acceptVisitor(o, DeepCloneVisitor.createDeepCloneVisitor());
}
/**
* Relink from orgTable to newTable. Correct references to table aliases.
*
* @param orgTable
* @param newTable
* @param o
* @return
*/
public static <T> T relinkTable(BaseQueryTable orgTable, BaseQueryTable newTable, T o)
{
return AbstractBaseQuery.acceptVisitor(o, new ReplaceVisitor(orgTable, newTable, true));
}
/**
* Find all occurrences of tables with the given name, regardless of the alias.
*
* @param name
* @return set of QueryTable
*/
public List<QueryTable> findTable(String name)
{
TableFinder visitor = new TableFinder(name);
acceptVisitor(visitor);
return visitor.getTables();
}
/**
* Replace references to orgTable with newTable.
*
* @param orgTable
* @param newTable
*/
public boolean relinkTable(BaseQueryTable orgTable, BaseQueryTable newTable)
{
ReplaceVisitor replaceVisitor = new ReplaceVisitor(orgTable, newTable, true);
acceptVisitor(replaceVisitor);
return replaceVisitor.found();
}
public Placeholder getPlaceholder(TablePlaceholderKey key)
{
return getPlaceholder(this, key);
}
public static Placeholder getPlaceholder(IVisitable visitable, TablePlaceholderKey key)
{
PlaceHolderFinder phf = new PlaceHolderFinder(key);
visitable.acceptVisitor(phf);
List<Object> placeHolders = phf.getPlaceHolders();
if (placeHolders.size() == 0)
{
return null;
}
return (Placeholder)placeHolders.get(0);
}
public boolean setPlaceholderValue(TablePlaceholderKey key, Object value)
{
return setPlaceholderValue(this, key, value);
}
public static boolean setPlaceholderValue(IVisitable visitable, TablePlaceholderKey key, Object value)
{
PlaceHolderSetter phs = new PlaceHolderSetter(key, value);
visitable.acceptVisitor(phs);
return phs.hasUpdated();
}
/**
* Visitor class for finding tables by name regardless of alias.
*
* @author rgansevles
*
*/
public static class TableFinder implements IVisitor
{
private final String name;
private final List<QueryTable> tables = new ArrayList<QueryTable>();
/**
* @param name
*/
public TableFinder(String name)
{
this.name = name;
}
public List<QueryTable> getTables()
{
return tables;
}
public Object visit(Object o)
{
if (o instanceof QueryTable)
{
QueryTable t = (QueryTable)o;
if (t.getName().equals(name))
{
tables.add(t);
}
}
return o;
}
}
/**
* Visitor class for finding place holders by key.
*
* @author rgansevles
*
*/
public static class PlaceHolderFinder implements IVisitor
{
private final TablePlaceholderKey key;
private final List<Object> placeHolders = new ArrayList<Object>();
PlaceHolderFinder(TablePlaceholderKey key)
{
this.key = key;
}
List<Object> getPlaceHolders()
{
return placeHolders;
}
public Object visit(Object o)
{
if (o instanceof Placeholder && (key.equals(((Placeholder)o).getKey())))
{
placeHolders.add(o);
}
return o;
}
}
public static List<String> getInvalidRangeConditions(ISQLCondition condition)
{
List<String> invalidRangeConditions = new ArrayList<String>();
if (condition != null)
{
CompareConditionVisitor betweenConditionVisitor = new AbstractBaseQuery.CompareConditionVisitor(IBaseSQLCondition.BETWEEN_OPERATOR);
AbstractBaseQuery.acceptVisitor(condition, betweenConditionVisitor);
List<CompareCondition> rangeConditions = betweenConditionVisitor.getResult();
IQuerySelectValue[] ccKey;
Object ccOperand2;
for (CompareCondition cc : rangeConditions)
{
ccKey = cc.getKeys();
ccOperand2 = cc.getOperand2();
if (ccKey != null && ccKey.length > 0 && ccOperand2 != null && ccOperand2 instanceof Object[]) // be safe
{
String columnName = ccKey[0].getColumn().getName();
Object[] values = (Object[])ccOperand2;
if (values.length > 1 && values[0] instanceof Comparable && values[1] instanceof Comparable &&
((Comparable<Object>)values[0]).compareTo(values[1]) > 0)
{
invalidRangeConditions.add(columnName);
}
}
}
}
return invalidRangeConditions;
}
/**
* Visitor class for setting place holder value.
*
* @author rgansevles
*
*/
public static class PlaceHolderSetter implements IVisitor
{
private final TablePlaceholderKey key;
private final Object value;
private boolean hasUpdated = false;
/**
* @param key
* @param value
*/
public PlaceHolderSetter(TablePlaceholderKey key, Object value)
{
this.key = key;
this.value = value;
}
public boolean hasUpdated()
{
return hasUpdated;
}
public Object visit(Object o)
{
if (o instanceof Placeholder && key.equals(((Placeholder)o).getKey()))
{
((Placeholder)o).setValue(value);
hasUpdated = true;
}
return o;
}
public Object getKey()
{
return key;
}
public Object getValue()
{
return value;
}
}
///////// serialization ////////////////
/**
* Return serialized replacement for this query.
*
* @return
*/
public abstract Object writeReplace();
static class CompareConditionVisitor implements IVisitor
{
private final int compareCondition;
private final List<CompareCondition> returnCompareConditions = new ArrayList<CompareCondition>();
CompareConditionVisitor(int compareCondition)
{
this.compareCondition = compareCondition;
}
public Object visit(Object o)
{
if (o instanceof CompareCondition && ((CompareCondition)o).getOperator() == compareCondition)
{
returnCompareConditions.add((CompareCondition)o);
}
return o;
}
List<CompareCondition> getResult()
{
return returnCompareConditions;
}
}
}