All Downloads are FREE. Search and download functionalities are using the official Maven repository.

iot.jcypher.domainquery.internal.QueryRecorder Maven / Gradle / Ivy

Go to download

Provides seamlessly integrated Java access to graph databases (Neo4J) at different levels of abstraction.

The newest version!
/************************************************************************
 * 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import iot.jcypher.domainquery.AbstractDomainQuery;
import iot.jcypher.domainquery.GDomainQuery;
import iot.jcypher.domainquery.InternalAccess;
import iot.jcypher.domainquery.api.APIAccess;
import iot.jcypher.domainquery.api.DomainObjectMatch;
import iot.jcypher.domainquery.internal.RecordedQuery.Invocation;
import iot.jcypher.domainquery.internal.RecordedQuery.Statement;
import iot.jcypher.query.values.MathFunctions;
import iot.jcypher.query.values.ValueAccess;
import iot.jcypher.query.values.ValueElement;

public class QueryRecorder {
	
	public static final String QUERY_ID = "q";
	
	private static ThreadLocal queriesPerThread =
			new ThreadLocal();
	public static ThreadLocal blockRecording =
			new ThreadLocal() {
				@Override
				protected Boolean initialValue() {
					return Boolean.FALSE;
				}
		
	};

	/**
	 * invocations on domainQuery (q)
	 * but stacked within e.g. a SELECT_FROM(...).ELEMENTS(...) statement.
	 * Must not be directly added to the root RecordedQuery
	 * but must be encapsulated in a sub statement
	 * @param on
	 * @param method
	 * @param result
	 * @param params
	 */
	public static void recordStackedInvocation(Object on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		recordInvocation(false, true, false, on, method, result, params);
	}
	
	/**
	 * invocations on domainQuery (q)
	 * but stacked within e.g. a SELECT_FROM(...).ELEMENTS(...) statement.
	 * Must not be directly added to the root RecordedQuery
	 * but must be encapsulated in a sub statement
	 * @param on
	 * @param method
	 * @param result
	 * @param params
	 */
	public static void recordStackedAssignment(Object on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		recordInvocation(true, true, false, on, method, result, params);
	}
	
	public static void recordInvocation(Object on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		recordInvocation(false, false, false, on, method, result, params);
	}
	
	public static void recordInvocationNoConcat(Object on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		recordInvocation(false, false, true, on, method, result, params);
	}
	
	public static void recordAssignment(Object on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		recordInvocation(true, false, false, on, method, result, params);
	}
	
	private static void recordInvocation(boolean assign, boolean subRoot, boolean noConcat, Object on, String method,
			Object result, Object... params) {
		// subRoot true means invocations on domainQuery (q)
		// but stacked within e.g. a SELECT_FROM(...).ELEMENTS(...) statement.
		// Must not be directly added to the root RecordedQuery
		// but must be encapsulated in a sub statement
		QueriesPerThread qpt = getCreateQueriesPerThread();
		RecQueryHolder rqh = null;
		if (noConcat) {
			rqh = createRecQueryHolder();
		} else {
			if (on instanceof AbstractDomainQuery)
				rqh = getRecQueryHolder((AbstractDomainQuery)on);
			else {
				rqh = qpt.getHolderRef(on);
				if (rqh == null)
					rqh = createRecQueryHolder();
			}
		}
		
		if (subRoot && rqh.root) {
			// the parameters have already been adopted to the root
			rqh.stackLastNStatements(assign, params == null ? 0 : params.length, on, method, result);
		} else {
			List parameters = new ArrayList(params.length);
			for (int i = 0; i < params.length; i++) {
				Object param = params[i];
				if (param instanceof Literal)
					parameters.add(rqh.recordedQuery.literal(((Literal)param).value));
				else if (param instanceof PlaceHolder) {
					Object val = ((PlaceHolder)param).value;
					if (val instanceof DomainObjectMatch) {
						String oid = rqh.object2IdMap.get(val);
						parameters.add(rqh.recordedQuery.doMatchRef(oid));
					} else {
						RecQueryHolder trqh = qpt.getHolderRef(val);
						if (trqh != null) {
							List adopted = adoptStatements(trqh, rqh);
							parameters.addAll(adopted);
							qpt.removeHolderRef(val);
						} else { // assume it is a literal (or a parameter)
							parameters.add(rqh.recordedQuery.literal(val));
						}
					}
				} else if (param instanceof Reference) {
					Object val = ((Reference)param).value;
					parameters.add(rqh.recordedQuery.reference(val, rqh.getNextRefId()));
				}
			}
			rqh.recordInvocation(assign, on, method, result, parameters);
			qpt.putHolderRef(result, rqh);
			if (rqh.root) {
				qpt.removeFromQuery2HolderMap(rqh, null, false, null); // don't remove root
			}
		}
		return;
	}
	
	private static List adoptStatements(RecQueryHolder from,
			RecQueryHolder to) {
		List params = from.recordedQuery.getStatements();
		for (Statement s : params) {
			adoptStatement(from, to, s);
		}
		return params;
	}
	
	private static void adoptStatement(RecQueryHolder from,
			RecQueryHolder to, Statement s) {
		if (s instanceof Invocation) {
			String or = ((RecordedQuery.Invocation)s).getOnObjectRef();
			Object o = from.id2ObjectMap.get(or);
			String id = to.object2IdMap.get(o);
			if (id == null) {
				id = to.getNextId();
				to.object2IdMap.put(o, id);
				to.id2ObjectMap.put(id, o);
			}
			((RecordedQuery.Invocation) s).setOnObjectRef(id);
			
			or = ((RecordedQuery.Invocation)s).getReturnObjectRef();
			o = from.id2ObjectMap.get(or);
			id = to.object2IdMap.get(o);
			if (id == null) {
				id = to.getNextId();
				to.object2IdMap.put(o, id);
				to.id2ObjectMap.put(id, o);
			}
			((RecordedQuery.Invocation) s).setReturnObjectRef(id);
			
			QueriesPerThread qpt = getCreateQueriesPerThread();
			List params = ((RecordedQuery.Invocation) s).getParams();
			for (Statement stmt : params) {
				RecQueryHolder nextFrom = qpt.getHolderForQuery(stmt.getRecordedQuery());
				if (nextFrom != null)
					adoptStatement(nextFrom, to, stmt);
			}
		}
		to.addAdopted(from);
	}

	public static void recordInvocationConditional(ValueElement on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		QueriesPerThread qpt = queriesPerThread.get();
		if (qpt != null && !qpt.isEmpty()) {
			Object dom = ValueAccess.getAnyHint(on, APIAccess.hintKey_dom);
			if (dom != null)
				recordInvocation(on, method, result, params);
		}
	}
	
	public static void recordInvocationConditional(MathFunctions on, String method, Object result, Object... params) {
		if (blockRecording.get())
			return;
		QueriesPerThread qpt = queriesPerThread.get();
		if (qpt != null && !qpt.isEmpty()) {
			Object dom = ValueAccess.getAnyHint(ValueAccess.getArgument(on), APIAccess.hintKey_dom);
			if (dom != null)
				recordInvocation(on, method, result, params);
		}
	}
	
	public static void recordInvocationReplace(DomainObjectMatch on, Object toReplace,
			String methodName) {
		if (blockRecording.get())
			return;
		QueriesPerThread qpt = getCreateQueriesPerThread();
		RecQueryHolder trqh = qpt.getHolderRef(toReplace);
		RecQueryHolder rqh = qpt.getHolderRef(on);
		if (trqh != null) {
			List stmts = trqh.recordedQuery.getStatements();
			for (Statement stmt : stmts) {
				if (stmt instanceof Invocation) {
					String onId = trqh.object2IdMap.get(on);
					if (onId == null) {
						onId = trqh.getNextId();
						trqh.object2IdMap.put(on, onId);
						trqh.id2ObjectMap.put(onId, on);
					}
					((RecordedQuery.Invocation)stmt).setOnObjectRef(onId);
					((RecordedQuery.Invocation)stmt).setMethod(methodName);
				}
			}
			//qpt.removeHolderRef(toReplace);
			if (rqh == null)
				qpt.putHolderRef(on, trqh);
			else
				rqh.addReplaced(trqh);
		}
	}
	
	public static void recordCreateQuery(AbstractDomainQuery query) {
		if (blockRecording.get())
			return;
		RecordedQuery rq = new RecordedQuery(query instanceof GDomainQuery);
		RecQueryHolder rqh = new RecQueryHolder(rq);
		rqh.root = true;
		getCreateQueriesPerThread().put(query, rqh);
	}
	
	public static void queryCompleted(AbstractDomainQuery query) {
		if (blockRecording.get())
			return;
		QueryExecutor qe = InternalAccess.getQueryExecutor((AbstractDomainQuery) query);
		boolean done = qe.queryCreationCompleted(false);
		if (!done) {
			QueriesPerThread qpt = queriesPerThread.get();
			if (qpt != null) {
				qpt.queryCompleted(query);
			}
		}
	}
	
	public static RecordedQuery getRecordedQuery(AbstractDomainQuery query) {
		if (blockRecording.get())
			return null;
		RecQueryHolder rqh = getRecQueryHolder(query);
		if (rqh != null)
			return rqh.recordedQuery;
		return null;
	}
	
	public static Literal literal(Object value) {
		return new Literal(value);
	}
	
	public static PlaceHolder placeHolder(Object value) {
		return new PlaceHolder(value);
	}
	
	public static Reference reference(Object value) {
		return new Reference(value);
	}
	
	private static RecQueryHolder getRecQueryHolder(AbstractDomainQuery on) {
		QueriesPerThread qpt = getCreateQueriesPerThread();
		return qpt.get(on);
	}
	
	private static RecQueryHolder createRecQueryHolder() {
		RecQueryHolder rqh = new RecQueryHolder(new RecordedQuery(false));
		return rqh;
	}

	public static QueriesPerThread getCreateQueriesPerThread() {
		QueriesPerThread qpt = queriesPerThread.get();
		if (qpt == null)  {
			qpt = new QueriesPerThread();
			queriesPerThread.set(qpt);
		}
		return qpt;
	}
	
	public static QueriesPerThread getQueriesPerThread() {
		return queriesPerThread.get();
	}
	
	/*******************************/
	public static class QueriesPerThread {
		private Map queries;
		private Map recHolderRefs;
		private Map query2HolderMap;
		
		private QueriesPerThread() {
			super();
			this.queries = new HashMap();
			this.recHolderRefs = new IdentityHashMap();
			this.query2HolderMap = new HashMap();
		}
		
		private void put(AbstractDomainQuery key, RecQueryHolder value) {
			this.queries.put(key, value);
		}
		
		private RecQueryHolder get(AbstractDomainQuery key) {
			RecQueryHolder ret = this.queries.get(key);
			return ret;
		}
		
		private void putHolderRef(Object key, RecQueryHolder value) {
			this.recHolderRefs.put(key, value);
			this.query2HolderMap.put(value.recordedQuery, value);
		}
		
		private RecQueryHolder getHolderRef(Object key) {
			RecQueryHolder ret = this.recHolderRefs.get(key);
			return ret;
		}
		
		private RecQueryHolder getHolderForQuery(RecordedQuery key) {
			RecQueryHolder ret = this.query2HolderMap.get(key);
			return ret;
		}
		
		private RecQueryHolder removeHolderRef(Object key) {
			RecQueryHolder ret = this.recHolderRefs.remove(key);
			//this.query2HolderMap.remove(ret.recordedQuery);
			return ret;
		}
		
		private void removeFromQuery2HolderMap(RecQueryHolder rqh, RecQueryHolder par,
				boolean removeRoot, Set recursionSet) {
			if (recursionSet == null)
				recursionSet = new HashSet();
			recursionSet.add(rqh);
			if (!rqh.root || removeRoot) {
				this.query2HolderMap.remove(rqh.recordedQuery);
				if (par != null)
					par.adopted.remove(rqh);
			}
			ArrayList adopted = new ArrayList();
			adopted.addAll(rqh.adopted);
			for (RecQueryHolder qh : adopted) {
				if (!recursionSet.contains(qh))
					this.removeFromQuery2HolderMap(qh, rqh, removeRoot, recursionSet);
			}
		}
		
		private boolean isEmpty() {
			return this.queries.isEmpty();
		}
		
		/**
		 * For testing purposes
		 * @return
		 */
		public boolean isCleared() {
			if (Settings.TEST_MODE) {
				System.gc();
				System.runFinalization();
			}
			return this.queries.isEmpty() &&
					this.recHolderRefs.isEmpty() &&
					this.query2HolderMap.isEmpty();
		}
		
		public void queryCompleted(AbstractDomainQuery query) {
			RecQueryHolder rqh = this.queries.remove(query);
			if (rqh != null) {
				this.removeFromQuery2HolderMap(rqh, null, true, null); // also remove root
				List toRemove = new ArrayList();
				Iterator> it = this.recHolderRefs.entrySet().iterator();
				while(it.hasNext()) {
					Entry e = it.next();
					if (e.getValue() == rqh)
						toRemove.add(e.getKey());
					else {
						if (rqh.inReplaced(e.getValue()))
							toRemove.add(e.getKey());
					}
				}
				for (Object o : toRemove) 
					this.recHolderRefs.remove(o);
			}
		}
		
		/**
		 * answer a map of DomainObjectMatch(es) to recorded query ids.
		 * 
Note: this destroys the original object2IdMap and may be called *
only after query recording has been completed. * @param q * @return */ public Map getDOM2IdMap(AbstractDomainQuery q) { RecQueryHolder rqh = this.get(q); if (rqh != null) { List remove = new ArrayList(); Iterator it = rqh.object2IdMap.keySet().iterator(); while(it.hasNext()) { Object o = it.next(); if (!(o instanceof DomainObjectMatch)) remove.add(o); } for(Object o : remove) rqh.object2IdMap.remove(o); return rqh.object2IdMap; } return null; } } /*******************************/ private static class RecQueryHolder { private static final String idPrefix = "obj"; private static final String refIdPrefix = "ref_"; private boolean root; private RecordedQuery recordedQuery; private Map object2IdMap; private Map id2ObjectMap; private List adopted; private List replaced; private int lastId; private int lastRefId; private RecQueryHolder(RecordedQuery recordedQuery) { super(); this.recordedQuery = recordedQuery; this.object2IdMap = new HashMap(); this.id2ObjectMap = new HashMap(); this.adopted = new ArrayList(); this.replaced = new ArrayList(); this.lastId = -1; this.lastRefId = -1; this.root = false; } private void recordInvocation(boolean assign, Object on, String method, Object result, List parameters) { String[] ids = getIds(on, result); if (assign) this.recordedQuery.addAssignment(ids[0], method, ids[1], parameters); else this.recordedQuery.addInvocation(ids[0], method, ids[1], parameters); } private String getRefId(Object ref) { String refId = this.object2IdMap.get(ref); if (refId == null) { refId = getNextRefId(); this.object2IdMap.put(ref, refId); this.id2ObjectMap.put(refId, ref); } return refId; } /** * @param on * @param result * @return [onId, resId] */ private String[] getIds(Object on, Object result) { String onId = this.object2IdMap.get(on); if (onId == null) { if (on instanceof AbstractDomainQuery) onId = QUERY_ID; else onId = getNextId(); this.object2IdMap.put(on, onId); this.id2ObjectMap.put(onId, on); } String resId = this.object2IdMap.get(result); if (resId == null) { resId = getNextId(); this.object2IdMap.put(result, resId); this.id2ObjectMap.put(resId, result); } return new String[]{onId, resId}; } private String getNextId() { this.lastId++; return idPrefix.concat(String.valueOf(this.lastId)); } private String getNextRefId() { this.lastRefId++; return refIdPrefix.concat(String.valueOf(this.lastRefId)); } private void addAdopted(RecQueryHolder qh) { if (!this.adopted.contains(qh)) this.adopted.add(qh); } private void addReplaced(RecQueryHolder qh) { if (!this.replaced.contains(qh)) this.replaced.add(qh); } private boolean inReplaced(RecQueryHolder rqh) { int idx = this.replaced.indexOf(rqh); return idx >= 0; } private void stackLastNStatements(boolean assign, int n, Object on, String method, Object result) { List stmts = new ArrayList(n); if (n > 0) { int addOffs = 0; Statement stmt = null; Statement following = null; List qstmts = this.recordedQuery.getStatements(); int offs = qstmts.size() - 1; for (int i = 0; i < n +addOffs; i++) { stmt = qstmts.remove(offs - i); stmts.add(0, stmt); // adjustment for concatenated statements if (following instanceof Invocation && stmt instanceof Invocation) { if (((Invocation)following).getOnObjectRef().equals( ((Invocation)stmt).getReturnObjectRef())) addOffs++; } following = stmt; } // now follow the concatenations of the first stacked statement offs = qstmts.size() - 1; while (offs >= 0) { Statement prev = qstmts.get(offs); boolean goOn = false; if (prev instanceof Invocation && stmt instanceof Invocation) { if (((Invocation)stmt).getOnObjectRef().equals( ((Invocation)prev).getReturnObjectRef())) { qstmts.remove(offs); stmts.add(0, prev); stmt = prev; offs--; goOn = true; } } if (!goOn) break; } } String[] ids = getIds(on, result); if (assign) this.recordedQuery.addAssignment(ids[0], method, ids[1], stmts); else this.recordedQuery.addInvocation(ids[0], method, ids[1], stmts); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.recordedQuery.toString()); return sb.toString(); } } /*********************************/ public static class Literal { private Object value; public Literal(Object value) { super(); this.value = value; } } /*********************************/ public static class PlaceHolder { private Object value; public PlaceHolder(Object value) { super(); this.value = value; } } /*********************************/ public static class Reference { private Object value; public Reference(Object value) { super(); this.value = value; } } }