/************************************************************************
* Copyright (c) 2015-2016 IoT-Solutions e.U.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
************************************************************************/
package iot.jcypher.domainquery.internal;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import iot.jcypher.domain.IDomainAccess;
import iot.jcypher.domain.IGenericDomainAccess;
import iot.jcypher.domain.internal.IIntDomainAccess;
import iot.jcypher.domainquery.DomainQuery;
import iot.jcypher.domainquery.GDomainQuery;
import iot.jcypher.domainquery.InternalAccess;
import iot.jcypher.domainquery.api.DomainObjectMatch;
import iot.jcypher.domainquery.ast.Parameter;
import iot.jcypher.domainquery.internal.RecordedQuery.DOMatchRef;
import iot.jcypher.domainquery.internal.RecordedQuery.Invocation;
import iot.jcypher.domainquery.internal.RecordedQuery.Literal;
import iot.jcypher.domainquery.internal.RecordedQuery.Reference;
import iot.jcypher.domainquery.internal.RecordedQuery.Statement;
public class RecordedQueryPlayer {
private Map<String, Object> id2ObjectMap;
private ReplayedQueryContext replayedQueryContext;
private boolean generic;
private boolean createNew;
public RecordedQueryPlayer() {
this(false);
}
public RecordedQueryPlayer(boolean createNew) {
super();
this.createNew = createNew;
this.id2ObjectMap = new HashMap<String, Object>();
}
/**
* Create a domain query from a recorded query.
* @param recordedQuery
* @param domainAccess
* @return
*/
public DomainQuery replayQuery(RecordedQuery recordedQuery, IDomainAccess domainAccess) {
Boolean br_old = null;
DomainQuery query;
try {
if (!Settings.TEST_MODE) {
br_old = QueryRecorder.blockRecording.get();
if (this.createNew)
QueryRecorder.blockRecording.set(Boolean.FALSE);
else
QueryRecorder.blockRecording.set(Boolean.TRUE);
}
this.generic = false;
this.replayedQueryContext = new ReplayedQueryContext(recordedQuery);
query = ((IIntDomainAccess)domainAccess).getInternalDomainAccess().createRecordedQuery(this.replayedQueryContext,
this.createNew); // do record
this.id2ObjectMap.put(QueryRecorder.QUERY_ID, query);
for (Statement stmt : recordedQuery.getStatements()) {
replayStatement(stmt);
}
Map<String, Parameter> params = recordedQuery.getParameters();
if (params != null) {
QueryExecutor qe = InternalAccess.getQueryExecutor(query);
Iterator<Parameter> pit = params.values().iterator();
while(pit.hasNext()) {
Parameter param = pit.next();
qe.addParameter(param);
}
}
} finally {
if (!Settings.TEST_MODE)
QueryRecorder.blockRecording.set(br_old);
}
return query;
}
/**
* Create a generic domain query from a recorded query.
* @param recordedQuery
* @param domainAccess
* @return
*/
public GDomainQuery replayGenericQuery(RecordedQuery recordedQuery, IGenericDomainAccess domainAccess) {
Boolean br_old = null;
GDomainQuery query;
try {
if (!Settings.TEST_MODE) {
br_old = QueryRecorder.blockRecording.get();
if (this.createNew)
QueryRecorder.blockRecording.set(Boolean.FALSE);
else
QueryRecorder.blockRecording.set(Boolean.TRUE);
}
this.generic = true;
this.replayedQueryContext = new ReplayedQueryContext(recordedQuery);
query = ((IIntDomainAccess)domainAccess).getInternalDomainAccess().createRecordedGenQuery(this.replayedQueryContext,
this.createNew); // do record
this.id2ObjectMap.put(QueryRecorder.QUERY_ID, query);
for (Statement stmt : recordedQuery.getStatements()) {
replayStatement(stmt);
}
} finally {
if (!Settings.TEST_MODE)
QueryRecorder.blockRecording.set(br_old);
}
return query;
}
private Object replayStatement(Statement stmt) {
Object ret = null;
if (stmt instanceof Literal)
ret = ((Literal)stmt).getValue();
else if (stmt instanceof Invocation) {
ret = replayInvocation((Invocation) stmt);
} else if (stmt instanceof DOMatchRef) {
String oid = ((DOMatchRef)stmt).getRef();
ret = this.id2ObjectMap.get(oid);
} else if (stmt instanceof Reference) {
ret = ((Reference)stmt).getValue();
addToId2ObjectMap(((Reference)stmt).getRefId(), ret);
}
return ret;
}
private Object replayInvocation(Invocation invocation) {
try {
List<Object> params = new ArrayList<Object>(invocation.getParams().size());
Class<?>[] args = new Class[invocation.getParams().size()];
int idx = 0;
for (Statement stmt : invocation.getParams()) {
Object param = replayStatement(stmt);
params.add(param);
args[idx] = param.getClass();
idx++;
}
args = concatParams(invocation.getParams(), params, args);
Object on = this.id2ObjectMap.get(invocation.getOnObjectRef());
Method mthd = findMethod(invocation.getMethod(), on, args, params);
Object ret = mthd.invoke(on, params.toArray());
addToId2ObjectMap(invocation.getReturnObjectRef(), ret);
return ret;
} catch(Throwable e) {
if (e instanceof RuntimeException)
throw (RuntimeException)e;
else
throw new RuntimeException(e);
}
}
private Class<?>[] concatParams(List<Statement> params, List<Object> params2, Class<?>[] args) {
Statement prev = null;
boolean concatenated = false;
int idx = 0;
for (Statement param : params) {
if (prev instanceof Invocation) {
if (param instanceof Invocation) {
if (((Invocation)param).getOnObjectRef().equals(((Invocation)prev).getReturnObjectRef())) {
idx--;
params2.remove(idx);
concatenated = true;
}
}
}
prev = param;
idx++;
}
if (concatenated) {
Class<?>[] nArgs = new Class<?>[params2.size()];
for (int i = 0; i < params2.size(); i++) {
nArgs[i] = params2.get(i).getClass();
}
return nArgs;
} else
return args;
}
private Method findMethod(String methodName, Object on, Class<?>[] args, List<Object> params)
throws Throwable{
Method ret = null;
String mthdName = methodName;
if (methodName.equals("createMatch") && !this.generic) {
args[0] = Class.class;
Class<?> cls = Class.forName(params.remove(0).toString());
params.add(cls);
} else if (methodName.equals("TO")) {
if (!this.generic) {
args[0] = Class.class;
Class<?> cls = Class.forName(params.remove(0).toString());
params.add(cls);
} else {
mthdName = "TO_GENERIC";
}
} else if (methodName.equals("TO_GENERIC")) {
if (!this.generic) {
args[0] = Class.class;
Class<?> cls = Class.forName(params.remove(0).toString());
params.add(cls);
mthdName = "TO";
}
} else if (methodName.equals("AS")) {
args[0] = Class.class;
Class<?> cls = Class.forName(params.remove(0).toString());
params.add(cls);
} else if (methodName.equals("createMatchFor") && !this.generic) {
if (args.length == 2) {
args[1] = Class.class;
Class<?> cls = Class.forName(params.remove(1).toString());
params.add(cls);
}
}
try {
ret = on.getClass().getMethod(mthdName, args);
} catch(NoSuchMethodException e) {
Method[] mthds = on.getClass().getMethods();
for (Method mthd : mthds) {
if (mthd.getName().equals(mthdName)) {
boolean matchVarTypes = false;
Class<?>[] types = mthd.getParameterTypes();
if (types.length != args.length || (types.length > 0 && types[types.length - 1].isArray())) { // handle variable arguments
if (types.length > 0 && types[types.length - 1].isArray()) {
if (args.length == 0 && types.length == 1)
matchVarTypes = true;
else if (args.length > 0) {
if (types[types.length - 1].getComponentType().isAssignableFrom(args[args.length - 1]))
matchVarTypes = true;
else if (equalPrimitives(types[types.length - 1].getComponentType(), args[args.length - 1]))
matchVarTypes = true;
}
}
}
if (matchVarTypes) {
// adjust variable parameters
int vparSize = params.size() - (types.length - 1);
Object paramsArray = Array.newInstance(types[types.length - 1].getComponentType(), vparSize);
for (int i = params.size() - 1; i >= types.length - 1; i--) {
int idx = i - (types.length - 1);
Array.set(paramsArray, idx, params.remove(i));
}
params.add(paramsArray);
ret = mthd;
break;
}
if (types.length == args.length) {
boolean same = true;
for (int i = 0; i < types.length; i++) {
if (!types[i].isAssignableFrom(args[i])) {
if (!equalPrimitives(types[i], args[i])) {
same = false;
break;
}
}
}
if (same) {
ret = mthd;
break;
}
}
}
}
if (ret == null)
throw e;
}
return ret;
}
private boolean equalPrimitives(Class<?> prim1, Class<?> prim2) {
if (prim1.isPrimitive()) {
if (Integer.class == prim2)
return Integer.TYPE == prim1;
else if (Long.class == prim2)
return Long.TYPE == prim1;
else if (Short.class == prim2)
return Short.TYPE == prim1;
else if (Float.class == prim2)
return Float.TYPE == prim1;
else if (Double.class == prim2)
return Double.TYPE == prim1;
else if (Boolean.class == prim2)
return Boolean.TYPE == prim1;
}
return false;
}
private void addToId2ObjectMap(String id, Object value) {
this.id2ObjectMap.put(id, value);
if (value instanceof DomainObjectMatch<?>)
this.replayedQueryContext.addDomainObjectMatch(id, (DomainObjectMatch<?>) value);
}
}