/************************************************************************ * Copyright (c) 2014-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.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import iot.jcypher.concurrency.QExecution; import iot.jcypher.concurrency.QExecution.ExecType; import iot.jcypher.domain.IDomainAccess; import iot.jcypher.domain.SyncInfo; import iot.jcypher.domain.internal.CurrentDomain; import iot.jcypher.domain.internal.DomainAccess.InternalDomainAccess; import iot.jcypher.domain.internal.IIntDomainAccess; import iot.jcypher.domain.internal.SkipLimitCalc; import iot.jcypher.domain.internal.SkipLimitCalc.SkipsLimits; import iot.jcypher.domain.mapping.CompoundObjectType; import iot.jcypher.domain.mapping.FieldMapping; import iot.jcypher.domain.mapping.MappingUtil; import iot.jcypher.domain.mapping.ObjectMapping; import iot.jcypher.domain.mapping.surrogate.Array; import iot.jcypher.domain.mapping.surrogate.Collection; import iot.jcypher.domainquery.AbstractDomainQuery; import iot.jcypher.domainquery.CountQueryResult; import iot.jcypher.domainquery.DomainQueryResult; import iot.jcypher.domainquery.InternalAccess; import iot.jcypher.domainquery.api.APIAccess; import iot.jcypher.domainquery.api.Count; import iot.jcypher.domainquery.api.DomainObjectMatch; import iot.jcypher.domainquery.api.IPredicateOperand1; import iot.jcypher.domainquery.ast.CollectExpression; import iot.jcypher.domainquery.ast.ConcatenateExpression; import iot.jcypher.domainquery.ast.ConcatenateExpression.Concatenator; import iot.jcypher.domainquery.ast.FromPreviousQueryExpression; import iot.jcypher.domainquery.ast.IASTObject; import iot.jcypher.domainquery.ast.OrderExpression; import iot.jcypher.domainquery.ast.OrderExpression.OrderBy; import iot.jcypher.domainquery.ast.Parameter; import iot.jcypher.domainquery.ast.PredicateExpression; import iot.jcypher.domainquery.ast.PredicateExpression.Operator; import iot.jcypher.domainquery.ast.SelectExpression; import iot.jcypher.domainquery.ast.TraversalExpression; import iot.jcypher.domainquery.ast.TraversalExpression.Step; import iot.jcypher.domainquery.ast.UnionExpression; import iot.jcypher.domainquery.internal.QueryRecorder.QueriesPerThread; import iot.jcypher.query.JcQuery; import iot.jcypher.query.JcQueryResult; import iot.jcypher.query.api.APIObject; import iot.jcypher.query.api.APIObjectAccess; import iot.jcypher.query.api.IClause; import iot.jcypher.query.api.pattern.Node; import iot.jcypher.query.api.pattern.Relation; import iot.jcypher.query.api.predicate.BooleanOperation; import iot.jcypher.query.api.predicate.Concat; import iot.jcypher.query.api.predicate.PFactory; import iot.jcypher.query.api.returns.RSortable; import iot.jcypher.query.ast.predicate.BooleanValue; import iot.jcypher.query.ast.predicate.IPredicateHolder; import iot.jcypher.query.ast.returns.ReturnElement; import iot.jcypher.query.ast.returns.ReturnExpression; import iot.jcypher.query.factories.clause.OPTIONAL_MATCH; import iot.jcypher.query.factories.clause.RETURN; import iot.jcypher.query.factories.clause.SEPARATE; import iot.jcypher.query.factories.clause.START; import iot.jcypher.query.factories.clause.WHERE; import iot.jcypher.query.factories.clause.WITH; import iot.jcypher.query.factories.xpression.C; import iot.jcypher.query.factories.xpression.I; import iot.jcypher.query.result.JcError; import iot.jcypher.query.result.JcResultException; import iot.jcypher.query.values.JcCollection; import iot.jcypher.query.values.JcNode; import iot.jcypher.query.values.JcNumber; import iot.jcypher.query.values.JcPath; import iot.jcypher.query.values.JcPrimitive; import iot.jcypher.query.values.JcValue; import iot.jcypher.query.values.ValueAccess; import iot.jcypher.query.values.ValueElement; import iot.jcypher.query.writer.Format; import iot.jcypher.util.QueriesPrintObserver.QueryToObserve; import iot.jcypher.util.Util; public class QueryExecutor implements IASTObjectsContainer { private static final String idPrefix = "id_"; private static final String countPrefix = "cnt_"; private static final String tmpNodePostPrefix = "_t"; private static final String collNodePostPrefix = "_c"; private static final String countXprPostPrefix = "_cnt"; private static final String collectNodePostfix = "_col"; private static final char separator = '_'; private IDomainAccess domainAccess; private List<IASTObject> astObjects; private List<OrderExpression> orders; private List<DomainObjectMatch<?>> domainObjectMatches; private Map<String, Parameter> parameters; private MappingInfo mappingInfo; private QueryContext queryResult; private QueryContext countResult; private RecordedQueryContext recordedQueryContext; private ReplayedQueryContext replayedQueryContext; public QueryExecutor(IDomainAccess domainAccess) { super(); this.domainAccess = domainAccess; this.astObjects = new ArrayList<IASTObject>(); this.domainObjectMatches = new ArrayList<DomainObjectMatch<?>>(); this.parameters = new HashMap<String, Parameter>(); } @Override public void addAstObject(IASTObject astObj) { this.astObjects.add(astObj); } public List<IASTObject> getAstObjects() { return astObjects; } public OrderExpression getOrderFor(DomainObjectMatch<?> dom) { if (this.orders == null) this.orders = new ArrayList<OrderExpression>(); OrderExpression ret = null; for (OrderExpression oe : this.orders) { if (oe.getObjectMatch().equals(dom)) { ret = oe; break; } } if (ret == null) { ret = new OrderExpression(dom); this.orders.add(ret); } return ret; } public List<DomainObjectMatch<?>> getDomainObjectMatches() { return domainObjectMatches; } /** * Get or create, if not exists, a query parameter. * @param name of the parameter * @return a query parameter */ public Parameter parameter(String name) { Parameter param = this.parameters.get(name); if (param == null) { param = new Parameter(name); this.parameters.put(name, param); } return param; } public Set<String> getParameterNames() { return this.parameters.keySet(); } public void addParameter(Parameter param) { this.parameters.put(param.getName(), param); } /** * Execute the domain query */ public void execute() { if (hasBeenReplayed()) { // delegate to the replayed query if (this.recordedQueryContext.queryResult == null) // only count query has been executed this.recordedQueryContext.queryResult = InternalAccess.getDomainQuery(this.recordedQueryContext.countResult) .execute(); return; } String pLab = ((IIntDomainAccess)domainAccess).getInternalDomainAccess().setDomainLabel(); QExecution qExec = ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess().getQExecution(); QExecution myQExec = null; boolean doReplay = false; if (qExec == null) { myQExec = new QExecution(ExecType.execute); ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess() .setQExecution(myQExec); } try { executeInternal(false); } catch(RuntimeException e) { if (myQExec != null && isReplayQuery(e)) doReplay = true; else throw e; } finally { CurrentDomain.setDomainLabel(pLab); if (qExec == null) ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess().setQExecution(null); } if (doReplay) replayQuery(myQExec); } /** * Execute the count query */ public void executeCount() { if (hasBeenReplayed()) { // delegate to the replayed query if (this.recordedQueryContext.countResult == null) // only dom query has been executed this.recordedQueryContext.countResult = InternalAccess.getDomainQuery(this.recordedQueryContext.queryResult) .executeCount(); return; } String pLab = ((IIntDomainAccess)domainAccess).getInternalDomainAccess().setDomainLabel(); QExecution qExec = ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess().getQExecution(); QExecution myQExec = null; boolean doReplay = false; if (qExec == null) { myQExec = new QExecution(ExecType.executeCount); ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess() .setQExecution(myQExec); } try { executeInternal(true); } catch(RuntimeException e) { if (myQExec != null && isReplayQuery(e)) doReplay = true; else throw e; } finally { CurrentDomain.setDomainLabel(pLab); if (qExec == null) ((IIntDomainAccess)this.domainAccess).getInternalDomainAccess().setQExecution(null); } if (doReplay) replayQuery(myQExec); } /** * answer true if this query has been replayed and therefore contains the replayed query result * @return */ public boolean hasBeenReplayed() { if (this.recordedQueryContext != null) { return this.recordedQueryContext.queryResult != null || this.recordedQueryContext.countResult != null; } return false; } private void replayQuery(QExecution myQExec) { if (this.recordedQueryContext != null) { RecordedQueryPlayer qp = new RecordedQueryPlayer(); AbstractDomainQuery q; if (this.recordedQueryContext.recordedQuery.isGeneric()) q = qp.replayGenericQuery(this.recordedQueryContext.recordedQuery, domainAccess.getGenericDomainAccess()); else q = qp.replayQuery(this.recordedQueryContext.recordedQuery, domainAccess); if (myQExec.geExecType() == ExecType.execute) this.recordedQueryContext.queryResult = q.execute(); else if (myQExec.geExecType() == ExecType.executeCount) this.recordedQueryContext.countResult = q.executeCount(); } } private boolean isReplayQuery(Throwable e) { if (e instanceof JcResultException) { List<JcError> errs = ((JcResultException)e).getErrors(); for (JcError err : errs) { if (err.getCodeOrType().equals(QExecution.REPLAY_QUERY)) return true; } } return false; } private void executeInternal(boolean execCount) { if (this.recordedQueryContext != null) this.recordedQueryContext.queryCompleted(); Boolean br_old = QueryRecorder.blockRecording.get(); try { QueryRecorder.blockRecording.set(Boolean.TRUE); QueryContext context = new QueryContext(execCount); QueryBuilder qb = new QueryBuilder(); List<JcQuery> queries = qb.buildQueries(context); Util.printQueries(queries, execCount ? QueryToObserve.COUNT_QUERY : QueryToObserve.DOM_QUERY, Format.PRETTY_1); List<JcQueryResult> results = ((IIntDomainAccess)domainAccess).getInternalDomainAccess(). execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { QueryRecorder.blockRecording.set(br_old); throw new JcResultException(errors); } // Util.printResults(results, execCount ? "COUNT QUERY" : "DOM QUERY", Format.PRETTY_1); if (context.execCount) { qb.extractCounts(results, context.resultsPerType); this.countResult = context; } else { qb.extractUniqueIds(results, context.resultsPerType); context.queryExecuted(); this.queryResult = context; } } finally { QueryRecorder.blockRecording.set(br_old); } } public MappingInfo getMappingInfo() { if (this.mappingInfo == null) this.mappingInfo = new MappingInfo(); return this.mappingInfo; } public List<IASTObject> getExpressionsFor(DomainObjectMatch<?> dom, List<IASTObject> astObjs) { QueryBuilder.ExpressionsPerDOM xpds = new QueryBuilder().buildExpressionsPerDOM(dom, astObjs, 0); return xpds.xPressions; } @SuppressWarnings("unchecked") public <T> List<T> loadResult(DomainObjectMatch<T> match) { if (APIAccess.isPageChanged(match)) // need to execute the query execute(); if (this.queryResult == null) throw new RuntimeException("query was not executed, call execute() on DomainQuery"); List<QueryContext.ResultsPerType> resPerTypeList = this.queryResult.resultsMap.get(match); if (resPerTypeList == null) throw new RuntimeException("DomainObjectMatch was not defined in this query"); Map<Class<?>, List<Long>> type2IdsMap = new HashMap<Class<?>, List<Long>>(); List<Long> allIds = new ArrayList<Long>(); List<T> ret = null; for (QueryContext.ResultsPerType resPerType : resPerTypeList) { if (resPerType.jcPrimitive != null) { if (ret == null) ret = new ArrayList<T>(); if (resPerType.simpleValues != null) ret.addAll((java.util.Collection<? extends T>) resPerType.simpleValues); } else { List<Long> ids = type2IdsMap.get(resPerType.type); boolean addList = false; if (ids == null) { ids = new ArrayList<Long>(); addList = true; } ids.addAll(resPerType.ids); allIds.addAll(resPerType.ids); if (ids.isEmpty()) addList = false; if (addList) type2IdsMap.put(resPerType.type, ids); } } if (ret == null) { // not null in case of primitive values long[] idsArray = new long[allIds.size()]; for (int i = 0; i < allIds.size(); i++) { idsArray[i] = allIds.get(i).longValue(); } ret = ((IIntDomainAccess)domainAccess).getInternalDomainAccess(). loadByIds(APIAccess.getDomainObjectType(match), type2IdsMap, -1, idsArray); } return ret; } @SuppressWarnings("unchecked") public <T> List<T> loadReplayedResult(DomainObjectMatch<T> match) { DomainObjectMatch<?> matching = this.recordedQueryContext.findMatchingTo(match); List<T> ret = (List<T>) this.recordedQueryContext.queryResult.resultOf(matching, true); // force reload return ret; } public long getCountResult(DomainObjectMatch<?> match) { long res = 0; if (this.countResult == null) throw new RuntimeException("query was not executed, call executeCount() on DomainQuery"); List<QueryContext.ResultsPerType> resPerTypeList = this.countResult.resultsMap.get(match); if (resPerTypeList == null) throw new RuntimeException("DomainObjectMatch was not defined in this query"); for (QueryContext.ResultsPerType resPerType : resPerTypeList) { res = res + resPerType.count; } return res; } public long getReplayedCountResult(DomainObjectMatch<?> match) { DomainObjectMatch<?> matching = this.recordedQueryContext.findMatchingTo(match); return this.recordedQueryContext.countResult.countOf(matching); } private List<Long> getIdsFor(DomainObjectMatch<?> match, Class<?> type) { if (APIAccess.isPageChanged(match)) // need to execute the query execute(); if (this.queryResult == null) throw new RuntimeException("query was not executed, call execute() on DomainQuery"); List<QueryContext.ResultsPerType> resPerTypeList = this.queryResult.resultsMap.get(match); if (resPerTypeList == null) throw new RuntimeException("DomainObjectMatch was not defined in this query"); List<Long> allIds = new ArrayList<Long>(); for (QueryContext.ResultsPerType resPerType : resPerTypeList) { if (resPerType.type == type && resPerType.jcPrimitive == null) { // not a primitive allIds.addAll(resPerType.ids); break; } } return allIds; } public QueryContext getQueryResult() { return queryResult; } private QueryContext loadCountResultIfNeeded() { if (this.countResult == null) { executeCount(); } return this.countResult; } public void recordQuery(RecordedQuery rq, AbstractDomainQuery q) { this.recordedQueryContext = new RecordedQueryContext(rq, rq != null ? QueryRecorder.getQueriesPerThread() : null, q); } public void replayQuery(ReplayedQueryContext rqc) { this.replayedQueryContext = rqc; } /** * Answer the context containing DomainObjectMatch(es) of a replayed query. * <br/>Answer null, if this is not a replayed query. * @return */ public ReplayedQueryContext getReplayedQueryContext() { return replayedQueryContext; } /** * Answer the recorded query. May return null. * @return */ public RecordedQuery getRecordedQuery() { if (this.recordedQueryContext != null) return this.recordedQueryContext.recordedQuery; return null; } /** * answer a map containing DomainObjectMatch to id entries * @return */ public Map<Object, String> getRecordedQueryObjects() { if (this.recordedQueryContext != null) { if (this.recordedQueryContext.object2IdMap != null) return this.recordedQueryContext.object2IdMap; else return this.recordedQueryContext.queriesPerThread.getDOM2IdMap( this.recordedQueryContext.domainQuery); } return null; } /** * If the query was created from a stored state, not if it was replayed because of an optimistic locking conflict, * then 'deleteReplayedQueryContext' is set to true and the replayedQueryContext is deleted. * After that the query is in the same state as when programmatically created for the first time. * @param deleteReplayedQueryContext * @return true if queryCompleted was actually called */ public boolean queryCreationCompleted(boolean deleteReplayedQueryContext) { boolean compl = false; if (this.recordedQueryContext != null) { this.recordedQueryContext.queryCompleted(); compl = true; } if (deleteReplayedQueryContext) this.replayedQueryContext = null; return compl; } @Override protected void finalize() throws Throwable { if (this.recordedQueryContext != null) this.recordedQueryContext.queryCompleted(); super.finalize(); } /************************************/ private class QueryBuilder { private ClauseBuilder clauseBuilder = new ClauseBuilder(); List<JcQuery> buildQueries(QueryContext context) { List<ClausesPerType> clausesPerTypeList = new ArrayList<ClausesPerType>(); List<ExpressionsPerDOM> xpressionsPerDom = new ArrayList<ExpressionsPerDOM>(); for (DomainObjectMatch<?> dom : domainObjectMatches) { ExpressionsPerDOM xpd = buildExpressionsPerDOM(dom, astObjects, 0); if (xpd != null) xpressionsPerDom.add(xpd); } xpressionsPerDom = orderByDependencies(xpressionsPerDom); ClauseBuilderContext cbContext = new ClauseBuilderContext(new ArrayList<String>()); for (ExpressionsPerDOM xpd : xpressionsPerDom) { clausesPerTypeList.addAll(this.clauseBuilder.buildClausesFor(xpd, cbContext, context.execCount)); } return buildQueriesInt(clausesPerTypeList, context, cbContext); } /** * @param dom * @param astObjs * @param mode 0 ... all expressions, 1 ... no count expressions, 2 ... count expressions only * @return */ private ExpressionsPerDOM buildExpressionsPerDOM(DomainObjectMatch<?> dom, List<IASTObject> astObjs, int mode) { ExpressionsPerDOM ret = null; StateContext stateContext = findXpressionsFor(dom, astObjs, mode); if (stateContext.state == State.HAS_XPRESSION) { ret = new ExpressionsPerDOM(dom, stateContext.candidates, stateContext.dependencies); } else if (stateContext.state == State.INIT) { stateContext.candidates.clear(); ret = new ExpressionsPerDOM(dom, stateContext.candidates, stateContext.dependencies); } return ret; } private List<JcQuery> buildQueriesInt(List<ClausesPerType> clausesPerTypeList, QueryContext context, ClauseBuilderContext cbContext) { List<JcQuery> ret = new ArrayList<JcQuery>(); int queryIndex = -1; // if split into multiple queries, due to use of LIMIT, SKIP, // which must be attached to a RETURN clause int startIdx = 0; while(startIdx < clausesPerTypeList.size()) { queryIndex++; List<IClause> clauses = new ArrayList<IClause>(); List<ClausesPerType> toReturn = new ArrayList<ClausesPerType>(); List<ClausesPerType> added = new ArrayList<ClausesPerType>(); OrderClausesHolder orderClausesHolder = new OrderClausesHolder(); boolean lastIsWith = false; int i; for (i = startIdx; i < clausesPerTypeList.size(); i++) { ClausesPerType cpt = clausesPerTypeList.get(i); if (cpt.collectionClauses != null) cpt.clauses = null; // reset for collection clauses // to ensure accurate with clauses if (cpt.valid) { boolean startNewQueryAfterThis = false; if (!context.execCount) { // execute full query (not count query) // we need to split up in multiple queries if (cpt.needDistinctQuery()) { // need to add it to the RETURN clause // does not work at the WITH clause if (i > startIdx) { // start a new query beginning with this one startIdx = i; // at the next run break; } orderClausesHolder.checkForOrdeClause(cpt); orderClausesHolder.whereNot = WHERE.NOT().valueOf(cpt.node).IS_NULL(); // i == startIdx (must be) // start a new query after this one startIdx = i + 1; startNewQueryAfterThis = true; } else orderClausesHolder.checkForOrdeClause(cpt); } if (cpt.startCountSelectXpr) { // we may need a distinct query. // A count expression is based on a traversal expression, which // will have been created already. It spoils the the traversal clones // for the select expression. To avoid this, we need a distinct query. if (i > startIdx) { // start a new query beginning with this one startIdx = i; // at the next run break; } } cpt.addDependencyClauses(clauses, added, orderClausesHolder, cbContext, context.execCount); clauses.addAll(cpt.getClauses(added)); if (APIAccess.isPartOfReturn(cpt.domainObjectMatch)) { toReturn.add(cpt); QueryContext.ResultsPerType resPerType = context.addFor(cpt, context.execCount); context.resultsPerType.add(resPerType); resPerType.queryIndex = queryIndex; } added.add(cpt); lastIsWith = cpt.lastIsWith; if (startNewQueryAfterThis) break; } else context.addEmptyFor(cpt); // did not produce a result } if (i == clausesPerTypeList.size()) // we are finished startIdx = i; List<IClause> returnClauses = buildReturnClauses(toReturn, context.execCount); // if we need to add WITH clauses, // add only those which will be returned orderClausesHolder.addClauses(clauses, lastIsWith, toReturn); clauses.addAll(returnClauses); IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]); JcQuery query = new JcQuery(); query.setClauses(clausesArray); ret.add(query); } return ret; } private List<IClause> buildReturnClauses(List<ClausesPerType> toReturn, boolean isCountQuery) { List<IClause> ret = new ArrayList<IClause>(); int idx = 0; for (ClausesPerType cpt : toReturn) { JcNode n = cpt.node; RSortable returnClause; if (isCountQuery) returnClause = RETURN.count().DISTINCT().value(n).AS(this.getJcNumber(countPrefix, n)); else { if (cpt.isCollectExpression) { if (idx == 0) returnClause = RETURN.DISTINCT().value(cpt.node); else returnClause = RETURN.value(cpt.node); } else { if (idx == 0) returnClause = RETURN.DISTINCT().value(n.id()).AS(this.getJcNumber(idPrefix, n)); else returnClause = RETURN.value(n.id()).AS(this.getJcNumber(idPrefix, n)); } // add skip and limit if required if (cpt.pageOffset > 0) returnClause = (RSortable)returnClause.SKIP(cpt.pageOffset); if (cpt.pageLength >= 0) returnClause = (RSortable)returnClause.LIMIT(cpt.pageLength); } ret.add(returnClause); idx++; } return ret; } void extractUniqueIds(List<JcQueryResult> results, List<QueryContext.ResultsPerType> resultsPerTypeList) { Set<Long> uniqueIds = new LinkedHashSet<Long>(); Set<Object> simpleVals = new LinkedHashSet<Object>(); for (QueryContext.ResultsPerType resPerType : resultsPerTypeList) { uniqueIds.clear(); simpleVals.clear(); JcQueryResult result = results.get(resPerType.queryIndex); if (MappingUtil.isSimpleType(resPerType.type)) { flatten(result.resultOf(resPerType.jcPrimitive), simpleVals); resPerType.simpleValues = new ArrayList<Object>(); resPerType.simpleValues.addAll(simpleVals); } else { List<BigDecimal> idList = result.resultOf(resPerType.jcNumber); for (BigDecimal bd : idList) { if (bd != null) { uniqueIds.add(bd.longValue()); } } resPerType.ids = new ArrayList<Long>(); resPerType.ids.addAll(uniqueIds); } } return; } @SuppressWarnings({ "unchecked", "rawtypes" }) private void flatten(List<?> resultOf, Set<Object> simpleVals) { for (Object elem : resultOf) { if (elem instanceof List<?>) simpleVals.addAll((java.util.Collection) elem); else simpleVals.add(elem); } } void extractCounts(List<JcQueryResult> results, List<QueryContext.ResultsPerType> resultsPerTypeList) { for (QueryContext.ResultsPerType resPerType : resultsPerTypeList) { JcQueryResult result = results.get(resPerType.queryIndex); List<BigDecimal> count = result.resultOf(resPerType.jcNumber); resPerType.count = count.get(0).longValue(); } } /** * may return null * @param cpt * @return */ private List<OrderBy> getOrderExpressionsFor(ClausesPerType cpt) { List<OrderBy> ret = null; OrderExpression oe = getOrderFor(cpt.domainObjectMatch); if (oe != null) { List<OrderBy> ocs = oe.getOrderCriterias(); for (OrderBy ob : ocs) { String attribName = ob.getAttributeName(); FieldMapping fm = getMappingInfo().getFieldMapping(attribName, cpt.domainObjectType); if (fm != null) { // field exists for type, so we can sort if (ret == null) ret = new ArrayList<OrderBy>(); ret.add(ob); } } } return ret; } private List<ExpressionsPerDOM> orderByDependencies( List<ExpressionsPerDOM> xpressionsPerDom) { List<ExpressionsPerDOM> ordered = new ArrayList<ExpressionsPerDOM>(xpressionsPerDom.size()); List<ExpressionsPerDOM> tryingToAdd = new ArrayList<ExpressionsPerDOM>(xpressionsPerDom.size()); for (ExpressionsPerDOM xpd : xpressionsPerDom) { addRecursive(xpd, xpressionsPerDom, ordered, tryingToAdd); } return ordered; } private void addRecursive(ExpressionsPerDOM add, List<ExpressionsPerDOM> xpressionsPerDom, List<ExpressionsPerDOM> ordered, List<ExpressionsPerDOM> tryingToAdd) { if (tryingToAdd.contains(add)) throw new RuntimeException("circular dependencies in WHERE clauses"); for (ExpressionsPerDOM xp : tryingToAdd) { xp.addDependencies(add.flattenedDependencies); xp.addDependency(add); } if (add.dependencies == null || add.dependencies.isEmpty()) { if (!ordered.contains(add)) ordered.add(add); } else { if (!ordered.contains(add)) { tryingToAdd.add(add); for (DomainObjectMatch<?> dom : add.dependencies) { ExpressionsPerDOM xpd = getXprPerDom(dom, xpressionsPerDom); addRecursive(xpd, xpressionsPerDom, ordered, tryingToAdd); } tryingToAdd.remove(add); ordered.add(add); } } } private ExpressionsPerDOM getXprPerDom(DomainObjectMatch<?> dom, List<ExpressionsPerDOM> xpressionsPerDom) { for (ExpressionsPerDOM xpd : xpressionsPerDom) { if (xpd.domainObjectMatch.equals(dom)) return xpd; } return null; } private SkipsLimits calcSkipsLimits(DomainObjectMatch<?> dom, int offset, int len) { QueryContext cRes = loadCountResultIfNeeded(); List<QueryContext.ResultsPerType> rpts = cRes.resultsMap.get(dom); List<Integer> counts = new ArrayList<Integer>(rpts.size()); for (QueryContext.ResultsPerType rpt : rpts) { counts.add((int)rpt.count); } SkipsLimits slc = SkipLimitCalc.calcSkipsLimits(counts, offset, len); return slc; } /** * @param value * @param op * @param paramPosition 1 or 2 */ private void testValidInOperation(Operator op, int paramPosition, PredicateExpression pred) { if (pred.getValue_1() instanceof Count) { if (!pred.isInCollectionExpression()) throw new RuntimeException("'COUNT' operation on a DomainObjectMatch is only valid within" + " a collection expression"); } if (op == Operator.CONTAINS) { if (pred.getValue_1() instanceof Count) throw new RuntimeException("'CONTAINS' cannot be applied to COUNT on DomainObject(s)"); else if (!(pred.getValue_1() instanceof JcCollection) && !(pred.isInCollectionExpression() && pred.getValue_1() instanceof DomainObjectMatch<?> && pred.getValue_2() instanceof DomainObjectMatch<?>)) { if (!(pred.getValue_1() instanceof ValueElement)) throw new RuntimeException("'CONTAINS' operation on two DomainObjectMatch(es) is only valid within" + " a collection expression"); else throw new RuntimeException("'CONTAINS' operation can only be performed on a 'Collection Attribute'." + "\nUse .collectionAttribute() on the DomainObjectMatch to access the attribute for the 'CONTAINS' operation!"); } return; // valid } Object value; if (paramPosition == 1) { value = pred.getValue_1(); if (value instanceof DomainObjectMatch<?>) { if (!(op == Operator.IN || op == Operator.IS_NULL)) throw new RuntimeException("invalid parameter 1 in WHERE clause [" + op.name() + "]"); } } else if (paramPosition == 2) { value = pred.getValue_2(); if (value instanceof DomainObjectMatch<?>) { if (op != Operator.IN) throw new RuntimeException("invalid parameter 2 in WHERE clause [" + op.name() + "]"); } } } private List<JcNode> collectNodes(DomainObjectMatch<?> dom, List<String> validNodes, Class<?> resultType) { List<JcNode> ret = new ArrayList<JcNode>(); if (resultType != null) { JcNode n = APIAccess.getNodeForType(dom, resultType); if (n != null) { String nnm = ValueAccess.getName(n); if (validNodes.contains(nnm)) ret.add(n); } } else { List<JcNode> nodes = APIAccess.getNodes((DomainObjectMatch<?>)dom); for(JcNode n : nodes) { String nnm = ValueAccess.getName(n); if (validNodes.contains(nnm)) ret.add(n); } } return ret; } /** * @param val * @param validNodes if != null, use list to filter nodes * @return a list of ValueElements */ @SuppressWarnings("unchecked") private List<Object> buildAllInstances(ValueElement ve, List<String> validNodes) { List<Object> ret = new ArrayList<Object>(); List<JcNode> validFor = null; if (ve != null) { ValueElement first = ValueAccess.findFirst(ve); if (first instanceof JcNode) { String nodeName = ValueAccess.getName((JcNode)first); if (validNodes.contains(nodeName)) ret.add(ve); if (validFor == null) { Object hint = ValueAccess.getAnyHint(ve, APIAccess.hintKey_validNodes); if (hint instanceof List<?>) { validFor = (List<JcNode>) hint; } } for(JcNode n : validFor) { String nnm = ValueAccess.getName(n); if (!nodeName.equals(nnm)) { // need to clone if (validNodes.contains(nnm)) ret.add(cloneVe(ve, first, n)); } } } else ret.add(ve); } return ret; } /** * @param ve * @param clausesPerType * @return the expression or its clone, or null if the expression is not valid for this type */ private ValueElement testAndCloneIfNeeded(ValueElement ve, ClausesPerType clausesPerType) { ValueElement ret = ve; if (ve != null) { ValueElement first = ValueAccess.findFirst(ve); if (first instanceof JcNode) { testValidForType((JcNode)first, ve, clausesPerType); if (clausesPerType.valid) { if(!(ValueAccess.getName((JcNode)first).equals(ValueAccess.getName(clausesPerType.node)))) { ret = cloneVe(ve, first, clausesPerType.node); } } else ret = null; } } return ret; } private ValueElement cloneVe(ValueElement ve, ValueElement first, ValueElement newFirst) { ValueElement ret = ve; ValueElement prev = ve; ValueElement nextCloned = null; while(prev != first) { ValueElement cloned = ValueAccess.cloneShallow(prev); if (nextCloned != null) ValueAccess.setPredecessor(nextCloned, cloned); else // in the first iteration ret = cloned; nextCloned = cloned; prev = ValueAccess.getPredecessor(prev); } if (nextCloned != null) // there was at least one iteration ValueAccess.setPredecessor(nextCloned, newFirst); else ret = newFirst; return ret; } @SuppressWarnings("unchecked") private void testValidForType(JcNode first, ValueElement ve, ClausesPerType clausesPerType) { if (ve == first) { clausesPerType.oneValid = true; return; } if (clausesPerType.previousOr || !Settings.strict) { // build the clause even if the expression will return no result clausesPerType.oneValid = true; // because it is concatenated by OR return; } Object hint = ValueAccess.getAnyHint(ve, APIAccess.hintKey_validNodes); if (hint instanceof List<?>) { List<JcNode> validFor = (List<JcNode>) hint; String nm = ValueAccess.getName(clausesPerType.node); for(JcNode n : validFor) { if (nm.startsWith(ValueAccess.getName(n))) { clausesPerType.oneValid = true; return; } } } clausesPerType.testForNextIsOr = true; return; } /** * @param dom * @param astObjs * @param mode 0 ... all expressions, 1 ... no count expressions, 2 ... count expressions only * @return */ private StateContext findXpressionsFor(DomainObjectMatch<?> dom, List<IASTObject> astObjs, int mode) { String baseNodeName = APIAccess.getBaseNodeName(dom); StateContext context = new StateContext(baseNodeName); for (int i = 0; i < astObjs.size(); i++) { IASTObject astObj = astObjs.get(i); testXpressionFor(dom, baseNodeName, astObj, context, mode); } if (context.blockCount != 0) throw new RuntimeException("bracket close mismatch"); if (context.state == State.HAS_XPRESSION) { removeEmptyBlocks(context.candidates); } return context; } private void removeEmptyBlocks(List<IASTObject> candidates) { List<Integer> toRemove = new ArrayList<Integer>(); List<Integer> orsToRemove = new ArrayList<Integer>(); List<Integer> candidatesIndices = new ArrayList<Integer>(); // key: openBracket index, value: or index Map<Integer, Integer> bracketToOr = new HashMap<Integer, Integer>(); int orIndexToTest = -1; boolean orMaybeValid = false; // or must not be at begin for (int i = 0; i < candidates.size(); i++) { IASTObject astObj = candidates.get(i); if (astObj instanceof ConcatenateExpression) { Concatenator concat = ((ConcatenateExpression)astObj).getConcatenator(); if (concat == Concatenator.BR_OPEN) { if (orIndexToTest != -1) // or may be before open bracket bracketToOr.put(i, orIndexToTest); orIndexToTest = -1; candidatesIndices.add(i); orMaybeValid = false; // or must not follow immediately after an open bracket } else if (concat == Concatenator.BR_CLOSE) { if (candidatesIndices.size() > 0) { // close follows immediately after open // empty block will be removed // idx is the matching open bracket index Integer idx = candidatesIndices.remove(candidatesIndices.size() - 1); Integer orIdx = bracketToOr.get(idx); if (orIdx != null) orIndexToTest = orIdx.intValue(); // must be tested if valid else orIndexToTest = -1; // if != -1was within the block that will be removed // it will be removed with the block so we can forget it // test and remove encapsulated pairs for (int j = toRemove.size() - 1; j >= 0; j--) { if (idx.intValue() < toRemove.get(j).intValue()) toRemove.remove(j); } toRemove.add(idx); // start toRemove.add(i); // end } else { orMaybeValid = true; // or can follow a closing bracket if (orIndexToTest != -1) orsToRemove.add(orIndexToTest); // or must not be immediately before a close bracket orIndexToTest = -1; } } else if (concat == Concatenator.OR) { if (!orMaybeValid) orsToRemove.add(i); else { orIndexToTest = i; // for test with next expression orMaybeValid = false; // no consecutive ors allowed } } } else { // not an empty block, contains at least one valid predicate expression candidatesIndices.clear(); orMaybeValid = true; // or can follow a predicate expression orIndexToTest = -1; } } if (orIndexToTest != -1) orsToRemove.add(orIndexToTest); // or must not be at the end // now remove the found empty blocks // indices are always pairwise for close and open brackets int prevCloseIndex = -1; // the previous close index paired with the actual open index // we always start with a close index for (int i = toRemove.size() - 1; i >= 0; i--) { int idx = toRemove.get(i).intValue(); if (prevCloseIndex == -1) // the actual is a close index candidates.remove(idx); else { // the actual is an open index for (int j = prevCloseIndex - 1; j >= idx; j--) { // remove all between the previous close index (exclusive) // and the actual open index (inclusive) candidates.remove(j); } int adjust = prevCloseIndex - idx + 1; // this range is removed // remove and adjust or indices for (int j = orsToRemove.size() - 1; j >= 0; j--) { int oidx = orsToRemove.get(j).intValue(); if (oidx > idx && oidx < prevCloseIndex) // remove orsToRemove.remove(j); else if (oidx > prevCloseIndex) orsToRemove.set(j, oidx - adjust); } } // toggle between close and open index (they are always pairwise) prevCloseIndex = prevCloseIndex == -1 ? idx : -1; } // remove invalid ors Collections.sort(orsToRemove); for (int i = orsToRemove.size() - 1; i >= 0; i--) { int idx = orsToRemove.get(i).intValue(); candidates.remove(idx); } } /** * @param baseNodeName * @param astObj * @param context * @param mode 0 ... all expressions, 1 ... no count expressions, 2 ... count expressions only */ private void testXpressionFor(DomainObjectMatch<?> dom, String baseNodeName, IASTObject astObj, StateContext context, int mode) { if (astObj instanceof PredicateExpression) { PredicateExpression pred = (PredicateExpression)astObj; boolean isXpr = false; IPredicateOperand1 val1 = pred.getValue_1(); String nodeName_1 = getNodeName(val1); int orRemoveState = 0; boolean isCount = val1 instanceof Count; if (mode == 0 || (isCount && mode == 2) || (!isCount && mode == 1)) { if (isCount) { DomainObjectMatch<?> udom = APIAccess.getDomainObjectMatch((Count)val1); if (APIAccess.isPartOfUnionExpression(udom, dom)) { nodeName_1 = APIAccess.getBaseNodeName(dom); } } if (nodeName_1 != null) { if (nodeName_1.indexOf(baseNodeName) == 0) { if (context.orToAdd != null) // add pending or context.candidates.add(context.orToAdd); context.candidates.add(astObj); isXpr = true; context.state = State.HAS_XPRESSION; orRemoveState = -1; } } } if (isXpr) { String nodeName_2 = getNodeName(pred.getValue_2()); if (nodeName_2 != null) { if (nodeName_2.indexOf(baseNodeName) < 0) { context.addDependency(nodeName_2); } } } context.orRemoveState = orRemoveState; context.orToAdd = null; // clear or, it has either been added already or it is invalid } else if (astObj instanceof ConcatenateExpression) { boolean isOr = false; int orRemoveState = -1; ConcatenateExpression conc = (ConcatenateExpression)astObj; if (conc.getConcatenator() == Concatenator.BR_OPEN) { context.blockCount++; if (context.orToAdd != null) // add pending or context.candidates.add(context.orToAdd); } else if (conc.getConcatenator() == Concatenator.BR_CLOSE) { if (context.blockCount <= 0) throw new RuntimeException("bracket close mismatch"); context.blockCount--; } else if (conc.getConcatenator() == Concatenator.OR) { orRemoveState = context.orRemoveState; isOr = true; } context.orToAdd = null; // clear or, it has either been added already or it is invalid if (orRemoveState != 0) { // prevoius was not foreign predicate expression if (isOr) context.orToAdd = astObj; // to test if next is foreign predicate expression else context.candidates.add(astObj); } context.orRemoveState = orRemoveState; } else if (astObj instanceof TraversalExpression) { TraversalExpression te = (TraversalExpression)astObj; String bName = APIAccess.getBaseNodeName(te.getEnd()); if (baseNodeName.equals(bName)) { context.candidates.add(astObj); context.state = State.HAS_XPRESSION; bName = APIAccess.getBaseNodeName(te.getStart()); context.addDependency(bName); } } else if (astObj instanceof SelectExpression<?>) { SelectExpression<?> se = (SelectExpression<?>)astObj; String bName = APIAccess.getBaseNodeName(se.getEnd()); if (baseNodeName.equals(bName)) { context.candidates.add(astObj); context.state = State.HAS_XPRESSION; bName = APIAccess.getBaseNodeName(se.getStart()); context.addDependency(bName); // add dependencies of predicate expressions within collection expression for(IASTObject ao : se.getAstObjects()) { if (ao instanceof PredicateExpression) { PredicateExpression pred = (PredicateExpression)ao; context.addDependency(getNodeName(pred.getValue_1())); context.addDependency(getNodeName(pred.getValue_2())); } } } } else if (astObj instanceof CollectExpression) { CollectExpression ce = (CollectExpression)astObj; String bName = APIAccess.getBaseNodeName(ce.getEnd()); if (baseNodeName.equals(bName)) { context.candidates.add(astObj); context.state = State.HAS_XPRESSION; bName = APIAccess.getBaseNodeName(ce.getStartDOM()); context.addDependency(bName); } } else if (astObj instanceof FromPreviousQueryExpression) { FromPreviousQueryExpression fpe = (FromPreviousQueryExpression)astObj; if (fpe.getActualMatch() == dom) { context.candidates.add(astObj); context.state = State.HAS_XPRESSION; } } } private String getNodeName(Object value) { if (value instanceof ValueElement) { ValueElement ve = (ValueElement)value; ValueElement first = ValueAccess.findFirst(ve); if (first instanceof JcNode) return ValueAccess.getName((JcNode)first); } else if (value instanceof DomainObjectMatch<?>) { return APIAccess.getBaseNodeName((DomainObjectMatch<?>)value); } else if (value instanceof Count) { return APIAccess.getBaseNodeName( APIAccess.getDomainObjectMatch((Count)value)); } return null; } private JcNumber getJcNumber(String prefix, JcNode n) { String nm = ValueAccess.getName(n); nm = nm.substring(APIAccess.nodePrefix.length()); nm = prefix.concat(nm); return new JcNumber(nm); } /*************************************/ private class OrderClausesHolder { private List<ClausesPerType> toOrder; private IClause whereNot; private void checkForOrdeClause(ClausesPerType cpt) { if (getOrderExpressionsFor(cpt) != null) { // if != null it contains at least one criteria if (this.toOrder == null) this.toOrder = new ArrayList<ClausesPerType>(); this.toOrder.add(cpt); } } private void addClauses(List<IClause> addTo, boolean lastIsWith, List<ClausesPerType> withToAdd) { if (this.toOrder != null || this.whereNot != null) { if (!lastIsWith) { // need to add WITH clauses for (ClausesPerType cpt : withToAdd) { addTo.add(WITH.value(cpt.node)); } } } if (this.toOrder != null) { for (ClausesPerType cpt : this.toOrder) { List<OrderBy> ocs = getOrderExpressionsFor(cpt); // returns an index of an RSortable int idx = this.indexOfOrderClause(cpt, addTo); if (idx != -1) { RSortable orderClause = (RSortable) addTo.get(idx); for (OrderBy ob : ocs) { if (ob.getDirection() == 0) orderClause = orderClause.ORDER_BY(ob.getAttributeName()); // TODO use property name else orderClause = orderClause.ORDER_BY_DESC(ob.getAttributeName()); } addTo.set(idx, orderClause); } } } if (this.whereNot != null) addTo.add(this.whereNot); } private int indexOfOrderClause(ClausesPerType cpt, List<IClause> clauses) { int strt = clauses.size() - 1; while(strt >= 0 && clauses.get(strt) instanceof SEPARATE) strt--; int idx = -1; for (int i = strt; i >= 0; i--) { IClause clause = clauses.get(i); if (clause instanceof RSortable) { // only RSortables can be ordered // can only be a ReturnExpression ReturnExpression rexp = (ReturnExpression)APIObjectAccess.getAstNode((APIObject) clause); if (rexp.getAlias() != null && ValueAccess.isSame(rexp.getAlias(), cpt.node)) { idx = i; break; } else if (rexp.getReturnValue() instanceof ReturnElement) { JcValue elem = ((ReturnElement)rexp.getReturnValue()).getElement(); if (ValueAccess.isSame(elem, cpt.node)) { idx = i; break; } } } else break; } return idx; } } /*************************************/ private class ClauseBuilder { private List<ClausesPerType> buildClausesFor(ExpressionsPerDOM xpd, ClauseBuilderContext cbContext, boolean calcCounts) { DomainObjectMatch<?> dom = xpd.domainObjectMatch; List<IASTObject> xpressionsForDom = xpd.xPressions; List<ClausesPerType> clausesPerTypeList = new ArrayList<ClausesPerType>(); List<JcNode> nodes = APIAccess.getNodes(dom); List<Class<?>> typeList = APIAccess.getTypeList(dom); int numTypes = typeList.size(); List<Integer> offsets; List<Integer> lens; int offset = calcCounts ? 0 : APIAccess.getPageOffset(dom); int len = calcCounts ? -1 : APIAccess.getPageLength(dom); if (numTypes > 1 && (offset > 0 || len >= 0) && APIAccess.isPageChanged(dom)) { SkipsLimits slc = calcSkipsLimits(dom, offset, len); offsets = slc.getOffsets(); lens = slc.getLengths(); } else { offsets = new ArrayList<Integer>(numTypes); lens = new ArrayList<Integer>(numTypes); if (numTypes == 1) { offsets.add(offset); lens.add(len); } else { for (int i = 0; i < numTypes; i++) { offsets.add(0); lens.add(-1); } } } int idx = 0; for (JcNode n : nodes) { ClausesPerType cpt = new ClausesPerType(n, dom, typeList.get(idx)); cpt.expressionsPerDOM = xpd; cpt.pageOffset = offsets.get(idx); cpt.pageLength = lens.get(idx); clausesPerTypeList.add(cpt); idx++; } cbContext.stepClausesCount = 0; for (IASTObject astObj : xpressionsForDom) { for (ClausesPerType clausePerType : clausesPerTypeList) { if (clausePerType.valid) { List<TraversalResult> travResults = forCollectionOwnersOf(clausePerType.domainObjectMatch, clausePerType.domainObjectType, cbContext); addClause(astObj, clausePerType, cbContext); // if traversal results exist and have not been created by this expression, // then add the current expression to the traversal result's ClausePerType if (travResults != null) { for (TraversalResult tr : travResults) { for (ClausesPerType cpt : tr.getClausesPerTypeList()) { // the first clauses to be added after the traversal clauses if (cpt.concatenator == null && cpt.concat == null) { cpt.concat = WHERE.BR_OPEN(); } addClause(astObj, cpt, cbContext); } } } } } } for (ClausesPerType clausePerType : clausesPerTypeList) { if (clausePerType.valid) { if (clausePerType.testForNextIsOr && !clausePerType.oneValid) // is not valid because we have reached clausePerType.valid = false; // the end, there is no next OR else { // add node names for which there is (will be) a match clause String nm = ValueAccess.getName(clausePerType.node); cbContext.validNodes.add(nm); } } } xpd.clausesPerTypes = clausesPerTypeList; return clausesPerTypeList; } private List<TraversalResult> forCollectionOwnersOf(DomainObjectMatch<?> travOwner, Class<?> travOwnerType, ClauseBuilderContext cbContext) { List<DomainObjectMatch<?>> collOwners = APIAccess.getCollectExpressionOwner(travOwner); if (collOwners != null) { List<TraversalResult> ret = new ArrayList<TraversalResult>(); for (DomainObjectMatch<?> collOwner : collOwners) { TraversalResult tr = cbContext.getTravResult(collOwner, travOwner, travOwnerType); if (tr != null) ret.add(tr); } return ret; } return null; } private void addClause(IASTObject astObj, ClausesPerType clausePerType, ClauseBuilderContext cbContext) { // The following if may only be executed after select clauses have // been added and when thereafter the first expression is added if (clausePerType.collectionClauses != null && !clausePerType.closeBracket) { // need to encapsulate the following expressions by brackets clausePerType.concat = clausePerType.concatenator.AND().BR_OPEN(); clausePerType.concatenator = null; clausePerType.closeBracket = true; } if (astObj instanceof ConcatenateExpression) { ConcatenateExpression conc = (ConcatenateExpression)astObj; if (conc.getConcatenator() == Concatenator.BR_OPEN) { if (clausePerType.concatenator == null) { if (clausePerType.concat == null) { if (clausePerType.traversalClauses != null) initTraversalConcat(clausePerType); else clausePerType.concat = WHERE.BR_OPEN(); clausePerType.previousOr = false; } else clausePerType.concat = clausePerType.concat.BR_OPEN(); } else { // a predicate expression or a block close was before clausePerType.concat = clausePerType.concatenator.AND().BR_OPEN(); clausePerType.concatenator = null; clausePerType.previousOr = false; } if (clausePerType.testForNextIsOr) clausePerType.valid = false; } else if (conc.getConcatenator() == Concatenator.BR_CLOSE) { if (clausePerType.concatenator == null) { throw new RuntimeException("illegal statement: " + conc.getConcatenator().name()); } else { clausePerType.concatenator = clausePerType.concatenator.BR_CLOSE(); } clausePerType.previousOr = false; } else if (conc.getConcatenator() == Concatenator.OR) { if (clausePerType.concatenator == null) { throw new RuntimeException("illegal statement: " + conc.getConcatenator().name()); } else { clausePerType.concat = clausePerType.concatenator.OR(); clausePerType.previousOr = true; clausePerType.testForNextIsOr = false; // or has occurred, we don't need further testing clausePerType.concatenator = null; } } } else if (astObj instanceof PredicateExpression) { if (clausePerType.testForNextIsOr) { clausePerType.valid = false; return; } PredicateExpression pred = (PredicateExpression)astObj; if (clausePerType.traversalClauses != null) { if (clausePerType.concat == null) initTraversalConcat(clausePerType); } Concat concat_1 = clausePerType.concat; if (clausePerType.concatenator != null) { concat_1 = clausePerType.concatenator.AND(); clausePerType.previousOr = false; } clausePerType.concatenator = buildPredicateExpression(pred, concat_1, clausePerType, cbContext); } else if (astObj instanceof TraversalExpression) { clausePerType.traversalClauses = createTraversalClauses( clausePerType.node, clausePerType, (TraversalExpression) astObj, cbContext, false, null).getAllClauses(); // no copy with path List<DomainObjectMatch<?>> collXOwner = APIAccess.getCollectExpressionOwner(((TraversalExpression)astObj).getEnd()); if (collXOwner != null) { // we need to produce copies of the traversal expression // for each collection expression which uses this traversal expression, // so that the result of the original traversal expression // will not be altered by the collection expression(s) for (DomainObjectMatch<?> dom : collXOwner) { String nnm = ValueAccess.getName(clausePerType.node).concat(collNodePostPrefix) .concat(APIAccess.getBaseNodeName(dom)); TraversalResult travResult = createTraversalClauses( new JcNode(nnm), clausePerType, (TraversalExpression) astObj, cbContext, true, dom); // copy with path cbContext.addTravClausesPerColl(dom, clausePerType.domainObjectMatch, clausePerType.domainObjectType, travResult); } } } else if (astObj instanceof SelectExpression<?>) { createAddSelectClauses(clausePerType, (SelectExpression<?>) astObj, cbContext); } else if (astObj instanceof CollectExpression) { createCollectClauses(clausePerType, (CollectExpression) astObj, cbContext); } else if (astObj instanceof FromPreviousQueryExpression) { createFromPrevQueryClauses(clausePerType, (FromPreviousQueryExpression)astObj, cbContext); } } private void initTraversalConcat(ClausesPerType cpt) { IClause cl = cpt.traversalClauses.get( cpt.traversalClauses.size() - 1); if (cl instanceof iot.jcypher.query.api.predicate.Concatenator) { cpt.concat = ((iot.jcypher.query.api.predicate.Concatenator)cl) .AND().BR_OPEN(); cpt.closeBracket = true; } } @SuppressWarnings("unchecked") private iot.jcypher.query.api.predicate.Concatenator buildPredicateExpression(PredicateExpression pred, Concat concat, ClausesPerType clausesPerType, ClauseBuilderContext cbContext) { iot.jcypher.query.api.predicate.Concatenator ret = null; // handle negations int neg = pred.getNegationCount(); Concat concat_1 = null; BooleanOperation booleanOp; Operator op = pred.getOperator(); ValueElement val_1 = null; IPredicateOperand1 v_1 = pred.getValue_1(); testValidInOperation(op, 1, pred); // throws an exception if not valid if (v_1 instanceof ValueElement) val_1 = testAndCloneIfNeeded((ValueElement)v_1, clausesPerType); else if (v_1 instanceof DomainObjectMatch<?> || v_1 instanceof Count) val_1 = clausesPerType.node; if (val_1 != null) { // if either really null or invalid Object val_2 = pred.getValue_2(); if (val_2 instanceof Parameter) val_2 = ((Parameter)val_2).getValue(); testValidInOperation(op, 2, pred); // throws an exception if not valid boolean val2IsDom = false; List<Object> val_2s = null; if (val_2 instanceof IPredicateOperand1) { // ValueElement or DomainObjectMatch if (val_2 instanceof DomainObjectMatch<?>) { // after test op must be IN or EQUALS val2IsDom = true; val_2s = new ArrayList<Object>(); val_2s.add(collectNodes((DomainObjectMatch<?>)val_2, cbContext.validNodes, clausesPerType.domainObjectType)); } else val_2s = buildAllInstances((ValueElement)val_2, cbContext.validNodes); } else if (val_2 != null) { val_2s = new ArrayList<Object>(); val_2s.add(val_2); } int cnt = val_2s != null ? val_2s.size() : 1; boolean negate = false; if (val2IsDom) { // after test op must be IN or EQUALS if (neg > 0) { neg--; negate = true; } } for (int i = neg; neg > 0; neg--) { if (i == neg) { // the first negation if (concat == null) concat_1 = (Concat)WHERE.NOT(); else concat_1 = (Concat)concat.NOT(); } else concat_1 = (Concat)concat_1.NOT(); } if (concat_1 == null) concat_1 = concat; if (cnt > 1 || val2IsDom) { // encapsulate by brackets if (concat_1 != null) concat_1 = concat_1.BR_OPEN(); else { // no negation(s) concat_1 = WHERE.BR_OPEN(); } } if (!val2IsDom) { if (concat_1 == null) booleanOp = WHERE.valueOf(val_1); else booleanOp = concat_1.valueOf(val_1); } else booleanOp = null; for (int i = 0; i < cnt; i++) { if (val_2s != null) val_2 = val_2s.get(i); if (op == Operator.EQUALS) ret = booleanOp.EQUALS(val_2); else if (op == Operator.GT) ret = booleanOp.GT(val_2); else if (op == Operator.GTE) ret = booleanOp.GTE(val_2); else if (op == Operator.IN) { if (val2IsDom) ret = createWhereIn(concat_1, val_1, (List<JcNode>) val_2, negate); else ret = booleanOp.IN_list(val_2); } else if (op == Operator.CONTAINS) { // value_1 must be a DomainObjactMatch, // must be in collection expression // validity test has already been done if (val2IsDom) ret = createWhereIn(concat_1, val_1, (List<JcNode>) val_2, negate); else ret = createContains(concat_1, (JcCollection) val_1, (Object[]) val_2, negate); } else if (op == Operator.IS_NULL) ret = booleanOp.IS_NULL(); else if (op == Operator.LIKE) ret = booleanOp.REGEX(val_2.toString()); else if (op == Operator.LT) ret = booleanOp.LT(val_2); else if (op == Operator.LTE) ret = booleanOp.LTE(val_2); else if (op == Operator.STARTS_WITH) ret = booleanOp.STARTS_WITH(val_2.toString()); else if (op == Operator.ENDS_WITH) ret = booleanOp.ENDS_WITH(val_2.toString()); else if (op == Operator.CONTAINS_STRING) ret = booleanOp.CONTAINS(val_2.toString()); if (i < cnt - 1) booleanOp = ret.OR().valueOf(val_1); } if (cnt > 1) { // encapsulate by brackets ret = ret.BR_CLOSE(); } } else clausesPerType.valid = false; return ret; } private iot.jcypher.query.api.predicate.Concatenator createContains( Concat concat, JcCollection val_1, Object[] val_2, boolean negate) { JcValue x = new JcValue("x"); iot.jcypher.query.api.predicate.Concatenator fx = null; if (val_2.length == 1) { if (concat != null) fx = concat.holdsTrue(I.forAny(x).IN(val_1).WHERE().valueOf(x).EQUALS(val_2[0])); else fx = WHERE.holdsTrue(I.forAny(x).IN(val_1).WHERE().valueOf(x).EQUALS(val_2[0])); } else if (val_2.length > 1) { Concat concat_1; if (concat != null) concat_1 = concat.BR_OPEN(); else concat_1 = WHERE.BR_OPEN(); for (int i = 0; i < val_2.length;i++) { if (fx == null) fx = concat_1.holdsTrue(I.forAny(x).IN(val_1).WHERE().valueOf(x).EQUALS(val_2[i])); else fx = fx.AND().holdsTrue(I.forAny(x).IN(val_1).WHERE().valueOf(x).EQUALS(val_2[i])); } fx = fx.BR_CLOSE(); } return fx; } private iot.jcypher.query.api.predicate.Concatenator createWhereIn( Concat concat, ValueElement val_1, List<JcNode> val_2, boolean not) { iot.jcypher.query.api.predicate.Concatenator booleanOp = null; Concat concat_1 = concat; if (val_2.isEmpty()) { iot.jcypher.query.ast.predicate.PredicateExpression px = (iot.jcypher.query.ast.predicate.PredicateExpression)APIObjectAccess.getAstNode(concat_1); IPredicateHolder ph = px.getLastPredicateHolder(); BooleanValue bv = new BooleanValue(false); ph.setPredicate(bv); booleanOp = PFactory.createConcatenator((iot.jcypher.query.ast.predicate.PredicateExpression) ph) .BR_CLOSE(); } else { int idx = 0; for (JcNode n : val_2) { if (idx == 0 && val_2.size() > 1) // encapsulate with brackets concat_1 = concat_1.BR_OPEN(); if (not) { if (idx == 0) booleanOp = concat_1.valueOf(n).IS_NULL().OR().NOT().valueOf(val_1).IN_list(n).BR_CLOSE(); else booleanOp = booleanOp.AND().BR_OPEN().valueOf(n).IS_NULL().OR().NOT().valueOf(val_1).IN_list(n).BR_CLOSE(); } else { if (idx == 0) booleanOp = concat_1.NOT().valueOf(n).IS_NULL().AND().valueOf(val_1).IN_list(n).BR_CLOSE(); else booleanOp.OR().BR_OPEN().NOT().valueOf(n).IS_NULL().AND().valueOf(val_1).IN_list(n).BR_CLOSE(); } if ((idx == val_2.size() - 1) && val_2.size() > 1) // encapsulate with brackets booleanOp = booleanOp.BR_CLOSE(); idx++; } } return booleanOp; } private TraversalResult createTraversalClauses(JcNode origEndNode, ClausesPerType clausesPerType, TraversalExpression travEx, ClauseBuilderContext cbContext, boolean copy, DomainObjectMatch<?> collOwner) { TraversalResult ret = new TraversalResult(); ret.expressionParts = new ArrayList<List<IClause>>(); String startBName = APIAccess.getBaseNodeName(travEx.getStart()); String endNodeLabel = getMappingInfo().getLabelForClass(clausesPerType.domainObjectType); String origEndNodeName = ValueAccess.getName(origEndNode); List<StepClause> stepCls = new ArrayList<StepClause>(); for (String validNodeName : cbContext.validNodes) { if (validNodeName.indexOf(startBName) == 0) { int tmpNodeIdx = cbContext.stepClausesCount + stepCls.size(); // only valid clauses are returned List<StepClause> partStepCls = buildStepClauses(travEx, tmpNodeIdx, validNodeName, endNodeLabel, origEndNodeName, copy); List<IClause> part = new ArrayList<IClause>(); for (StepClause sc : partStepCls) { part.add(SEPARATE.nextClause()); part.add(sc.getTotalMatchNode()); } ret.expressionParts.add(part); stepCls.addAll(partStepCls); } } cbContext.stepClausesCount = cbContext.stepClausesCount + stepCls.size(); if (stepCls.size() == 1) { // don't need to build union of multiple sets StepClause sc = stepCls.get(0); JcNode endNd = sc.getEndNode(); ValueAccess.setName(origEndNodeName, endNd); if (sc.jcPath != null) { ValueAccess.setName(pathFromNode(origEndNodeName), sc.jcPath); String startNodeName = ValueAccess.getName(sc.startNode); ret.addStartPath(startNodeName, sc.jcPath); ret.getClausesPerTypeList().add( new ClausesPerType(endNd, clausesPerType.domainObjectMatch, clausesPerType.domainObjectType)); cbContext.addCountFor(collOwner, clausesPerType.domainObjectMatch, startNodeName, endNd); } } else if (stepCls.size() > 1) { List<JcNode> tempNodes = new ArrayList<JcNode>(); for (StepClause sc : stepCls) { JcNode endNd = sc.getEndNode(); tempNodes.add(endNd); if (sc.jcPath != null) { ValueAccess.setName(pathFromNode( ValueAccess.getName(endNd)), sc.jcPath); String startNodeName = ValueAccess.getName(sc.startNode); ret.addStartPath(startNodeName, sc.jcPath); ret.getClausesPerTypeList().add( new ClausesPerType(endNd, clausesPerType.domainObjectMatch, clausesPerType.domainObjectType)); cbContext.addCountFor(collOwner, clausesPerType.domainObjectMatch, startNodeName, endNd); } } if (!copy) { // in this case the collection expression using // the copy works on each part-path in turn // therefore we don't need to build the union List<IClause> part = new ArrayList<IClause>(); part.add(SEPARATE.nextClause()); part.add(OPTIONAL_MATCH.node(origEndNode).label(endNodeLabel)); Concat concat = WHERE.BR_OPEN(); part.add(createWhereIn(concat, origEndNode, tempNodes, false)); ret.expressionParts.add(part); } } else { clausesPerType.valid = false; // throw new RuntimeException("attribute(s): " + invalidAttribs + " do(es) not exist " + // "in domain object type(s): " + inValidTypes); } return ret; } private void createFromPrevQueryClauses(ClausesPerType cpt, FromPreviousQueryExpression fpe, ClauseBuilderContext cbContext) { // this will always be the first expression for a DomainObjectMatch // or a ClausePerType respectively List<Long> ids = null; DomainObjectMatch<?> prev = fpe.getPreviousMatch(); @SuppressWarnings("unchecked") List<Object> prevobjs = (List<Object>) fpe.getPreviousObjects(); if (prev != null) { QueryExecutor qexec = APIAccess.getMappingInfo(prev).getQueryExecutor(); QueryContext qContext = qexec.getQueryResult(); if (qContext == null) { // not yet executed qexec.execute(); } ids = qexec.getIdsFor(prev, cpt.domainObjectType); } else if (prevobjs != null) { List<SyncInfo> syInfos = domainAccess.getSyncInfos(prevobjs); ids = new ArrayList<Long>(); for (SyncInfo si : syInfos) { long id = si.getId(); if (id == -1) throw new RuntimeException("Object was not retrieved by the underlying IDomainAccess"); ids.add(id); } } cpt.startIds = ids; } @SuppressWarnings("unchecked") private void createCollectClauses(ClausesPerType cpt, CollectExpression collEx, ClauseBuilderContext cbContext) { cpt.isCollectExpression = true; List<JcNode> nodes; Object hint = ValueAccess.getAnyHint(collEx.getAttribute(), APIAccess.hintKey_validNodes); if (hint instanceof List<?>) { nodes = (List<JcNode>) hint; nodes = cbContext.filterValidNodes(nodes); } else nodes = cbContext.filterValidNodes(APIAccess.getNodes(collEx.getStartDOM())); JcNode unionNode; JcValue val; if (nodes.size() > 0) { List<IClause> collectClauses = new ArrayList<IClause>(); if (nodes.size() > 1) { String unionName = APIAccess.getBaseNodeName(cpt.domainObjectMatch).concat(collectNodePostfix); unionNode = new JcNode(unionName); collectClauses.add(OPTIONAL_MATCH.node(unionNode)); collectClauses.add(createWhereIn(WHERE.BR_OPEN(), unionNode, nodes, false)); } else unionNode = nodes.get(0); JcCollection unionColl = new JcCollection(ValueAccess.getName(unionNode)); String tempName = APIAccess.getBaseNodeName(cpt.domainObjectMatch).concat(tmpNodePostPrefix); JcNode tempNode = new JcNode(tempName); ValueElement first = ValueAccess.findFirst(collEx.getAttribute()); val = (JcValue)cloneVe(collEx.getAttribute(), first, tempNode); collectClauses.add( WITH.collection(C.EXTRACT().valueOf(val).fromAll(tempNode).IN_list(unionColl)).AS(cpt.node)); cpt.withClausesAddIdxs = new ArrayList<Integer>(); cpt.withClausesAddIdxs.add(collectClauses.size()); cpt.collectionClauses = collectClauses; cpt.lastIsWith = true; } else cpt.valid = false; return; } private void createAddSelectClauses(ClausesPerType cpt, SelectExpression<?> selEx, ClauseBuilderContext cbContext) { // the select can return results only for valid nodes in start set, // because it selects from the start set. if (!cbContext.isNodeValid(APIAccess.getNodeForType(selEx.getStart(), cpt.domainObjectType))) { cpt.valid = false; return; } List<IClause> selectClauses = new ArrayList<IClause>(); // get the DOMs which are derived by traversal and which occur // as start in a predicate expression within this collection expression. // The appropriate traversal expressions have been cloned, so that // the predicate expression does not constrain the result of the original // traversal expression. // now add the predicate expression(s) to the clones List<DomainObjectMatch<?>> travDOMs = selEx.getTraversalResults(); Map<IASTObject, TraversalPathsPerDOM> astObject2TraversalPaths = new HashMap<IASTObject, TraversalPathsPerDOM>(); List<IClause> countWithClauses = new ArrayList<IClause>(); Map<Class<?>, CountIntersectsPerType> countIntersectsPerType = null; // needed for intersection in count List<IClause> selfWithClauses = new ArrayList<IClause>(); if (travDOMs != null) { for (DomainObjectMatch<?> dom : travDOMs) { int idx = 0; ExpressionsPerDOM xpd = null; ExpressionsPerDOM countXpd = null; TraversalPathsPerDOM tpd = new TraversalPathsPerDOM(); // for all types of the DomainObjectMatch List<Class<?>> typeList = APIAccess.getTypeList(dom); for (int i = 0; i < typeList.size(); i++) { JcNode node = APIAccess.getNodes(dom).get(i); if (cbContext.isNodeValid(node)) { Class<?> clazz = typeList.get(i); // The TraversalResult contains the cloned traversal clauses // (cloned for this collection expression) TraversalResult travResult = cbContext.getTravResult(cpt.domainObjectMatch, dom, clazz); if (travResult == null) { // null in case of union UnionExpression ue = APIAccess.getUnionExpression(dom); if (!ue.isUnion()) { // intersection needs special handling if (countIntersectsPerType == null) countIntersectsPerType = new HashMap<Class<?>, CountIntersectsPerType>(); CountIntersectsPerType cipt = countIntersectsPerType.get(clazz); if (cipt == null) { cipt = new CountIntersectsPerType(); countIntersectsPerType.put(clazz, cipt); } cipt.intersection = ue; cipt.buildIntersection = new ArrayList<ClausesPerType>(); cipt.intersectionDom = dom; cipt.countNodes = new ArrayList<JcNode>(); } } else { // add a traversal expression clone only once for each collection expression if (travResult.expressionsPerDOM == null) { travResult.countIntersectsPerType = countIntersectsPerType; // find all expressions within the collection expression // which act on the DomainObjectmatch dom. if (xpd == null) { // without count expressions xpd = buildExpressionsPerDOM(dom, selEx.getAstObjects(), 1); } if (countXpd == null) { // only count expressions countXpd = buildExpressionsPerDOM(dom, selEx.getAstObjects(), 2); } boolean isCount = false; if (countXpd != null && countXpd.xPressions.size() > 0) { cpt.startCountSelectXpr = true; isCount = true; } // also serves as marker, that the teraversal expression clone has been added travResult.expressionsPerDOM = xpd; travResult.countExpressionsPerDOM = countXpd; tpd.addAll(travResult.getStartPathMap()); int idx2 = 0; for (List<IClause> part : travResult.expressionParts) { if (!part.isEmpty()) { ClausesPerType iCpt = travResult.clausesPerTypeList.get(idx2); if (dom != iCpt.domainObjectMatch) throw new RuntimeException("internal error"); // if there are predicate expressions which already // constrain the result set of the traversal expression // these expressions are put within brackets // and so are the predicate expressions added by // the collection expression boolean bracket = false; if (iCpt.concatenator != null) { iCpt.concat = iCpt.concatenator.BR_CLOSE().AND().BR_OPEN(); iCpt.concatenator = null; bracket = true; } else iCpt.concat = WHERE.BR_OPEN(); // the path is needed, when a predicate expression is // added, expressed on the result of a traversal expression // (then the constraint will be expressed on head(nodes(path))) boolean needPath = false; // without count expressions for (IASTObject ao : xpd.xPressions) { if (ao instanceof PredicateExpression) { needPath = true; PredicateExpression pred = (PredicateExpression)ao; IPredicateOperand1 val1 = pred.getValue_1(); if (val1 instanceof ValueElement) { ValueElement ve = (ValueElement)val1; ve = cloneVe(ve, ValueAccess.findFirst(ve), iCpt.node); pred.setValue_1(ve); } } addClause(ao, iCpt, cbContext); if (idx == 0) astObject2TraversalPaths.put(ao, tpd); } if (needPath) { JcPath p = new JcPath(pathFromNode(ValueAccess.getName(iCpt.node))); countWithClauses.add(WITH.value(p)); } if (bracket) iCpt.concatenator = iCpt.concatenator.BR_CLOSE(); idx++; idx2++; selectClauses.addAll(part); if (iCpt.concatenator != null) selectClauses.add(iCpt.concatenator); selectClauses.add(SEPARATE.nextClause()); // now the count expressions for (IASTObject ao : countXpd.xPressions) { if (ao instanceof PredicateExpression) { boolean c_i = isCount && countIntersectsPerType != null && countIntersectsPerType.get(clazz) != null; if (c_i) countIntersectsPerType.get(clazz).buildIntersection.add(iCpt); addCountWithClause(iCpt.node, countWithClauses, selfWithClauses, selectClauses, cpt, c_i); } } } } // handle intersection in count expression CountIntersectsPerType cipt = countIntersectsPerType != null ? countIntersectsPerType.get(clazz) : null; if (isCount && cipt != null) { if (cipt.intersection.isLastOfSources(dom)) { // need to add intersection cipt.countNodes = addIntersectionInCount(cipt.intersectionDom, clazz, selectClauses, cipt.buildIntersection, countWithClauses, cpt, cipt.countNodes); } } } else { // mark the IASTObjects which have already been processed countIntersectsPerType = travResult.countIntersectsPerType; tpd.addAll(travResult.getStartPathMap()); // without count expressions, count expressions are handled differently for (IASTObject ao : travResult.expressionsPerDOM.xPressions) { astObject2TraversalPaths.put(ao, tpd); } } } } } } } String nodeLabel = getMappingInfo().getLabelForClass(cpt.domainObjectType); selectClauses.add( OPTIONAL_MATCH.node(cpt.node).label(nodeLabel) ); Concat concat = WHERE.BR_OPEN(); List<JcNode> nds = new ArrayList<JcNode>(); JcNode nd = APIAccess.getNodeForType(selEx.getStart(), cpt.domainObjectType); if (cbContext.isNodeValid(nd)) nds.add(nd); cpt.concatenator = createWhereIn(concat, cpt.node, nds, false); cbContext.stepClausesCount = 0; // scope: 0 .. constrain by head(nodes(path)) // 1 .. constraint on source set // 2 .. count expression // 3 .. finish (close open brackets) // 4 .. to force scope change int scope = -1; List<IASTObject> pendingBrOpen = new ArrayList<IASTObject>(); List<IASTObject> pendingBrClose = new ArrayList<IASTObject>(); IASTObject pendingOr = null; if (selEx.getAstObjects().size() > 1) { // everything after the 'containment in source expression' // are additional constraints and if more than one must be within brackets addClause(new ConcatenateExpression(Concatenator.BR_OPEN), cpt, cbContext); } for (IASTObject ao : selEx.getAstObjects()) { TraversalPathsPerDOM tpd = astObject2TraversalPaths.get(ao); if (tpd != null) { // constraint defined by a traversal expression // test if it was not additionally created as part of a count expression // because in that case the constraint is expressed in form of the count if (!(ao instanceof PredicateExpression && ((PredicateExpression)ao).isPartOfCount())) { if (!tpd.consumed) { tpd.consumed = true; // force a scope change int oldScope = (scope == 0) ? 4 : scope; scope = handleScopeClose(oldScope, 0, cpt, cbContext, pendingBrClose); pendingOr = handleScopeOpen(oldScope, 0, cpt, cbContext, pendingBrOpen, pendingOr); // add clauses for the traversals (match path start) // as head(nodes(path)) addTraversalConstraints2Select(tpd, nds, cpt); // brClose done by handleScopeClose() } } } else { // count expressions are handled differently if (ao instanceof PredicateExpression && ((PredicateExpression)ao).getValue_1() instanceof Count) { List<JcNode> countNodes = null; CountIntersectsPerType cipt = countIntersectsPerType != null ? countIntersectsPerType.get(cpt.domainObjectType) : null; if (cipt != null) countNodes = cipt.countNodes; if (countNodes == null) countNodes = cbContext.getCountsFor(cpt.domainObjectMatch, ((PredicateExpression)ao).getStartDOM(), ValueAccess.getName(nd)); int oldScope = scope; scope = handleScopeClose(scope, 2, cpt, cbContext, pendingBrClose); pendingOr = handleScopeOpen(oldScope, 2, cpt, cbContext, pendingBrOpen, pendingOr); if (pendingOr != null) { addClause(pendingOr, cpt, cbContext); pendingOr = null; } if (countNodes.size() > 0) { JcNumber prevNum = null; for (JcNode n : countNodes) { String alias = ValueAccess.getName(n).concat(countXprPostPrefix); JcNumber num = new JcNumber(alias); if (prevNum != null) prevNum = prevNum.plus(num); else prevNum = num; } PredicateExpression newPred = ((PredicateExpression)ao).createCopy(); newPred.setValue_1(prevNum); addClause(newPred, cpt, cbContext); // brClose done by handleScopeClose() } else { cpt.concat = cpt.concat.BR_OPEN(); iot.jcypher.query.ast.predicate.PredicateExpression px = (iot.jcypher.query.ast.predicate.PredicateExpression)APIObjectAccess.getAstNode(cpt.concat); BooleanValue bv = new BooleanValue(false); px.setPredicate(bv); cpt.concatenator = PFactory.createConcatenator(px) .BR_CLOSE(); } } else { if (ao instanceof ConcatenateExpression) { if (((ConcatenateExpression)ao).getConcatenator() == Concatenator.OR) { pendingOr = ao; } else if (((ConcatenateExpression)ao).getConcatenator() == Concatenator.BR_OPEN) { pendingBrOpen.add(ao); } else if (((ConcatenateExpression)ao).getConcatenator() == Concatenator.BR_CLOSE) { pendingBrClose.add(ao); } } else if (ao instanceof PredicateExpression) { int oldScope = scope; scope = handleScopeClose(scope, 1, cpt, cbContext, pendingBrClose); pendingOr = handleScopeOpen(oldScope, 1, cpt, cbContext, pendingBrOpen, pendingOr); handleBrackets(cpt, cbContext, pendingBrClose); if (pendingOr != null) { addClause(pendingOr, cpt, cbContext); pendingOr = null; } handleBrackets(cpt, cbContext, pendingBrOpen); addClause(ao, cpt, cbContext); // the expression is valid within the select expression, // as it must be expressed either on the source set // or on a set directly derived from the source set by traversal; // we must avoid testing validity afterwards cpt.testForNextIsOr = false; } } if(!cpt.valid) break; } } if (cpt.concatenator == null) { // no select expression content (e.g traversal xpr) added cpt.valid = false; return; } else handleScopeClose(scope, 3, cpt, cbContext, pendingBrClose); if (selEx.getAstObjects().size() > 1) addClause(new ConcatenateExpression(Concatenator.BR_CLOSE), cpt, cbContext); cpt.collectionClauses = selectClauses; } private List<JcNode> addIntersectionInCount(DomainObjectMatch<?> intersectionDom, Class<?> clazz, List<IClause> clauses, List<ClausesPerType> buildIntersection, List<IClause> countWithClauses, ClausesPerType cpt, List<JcNode> counNodes) { List<JcNode> ret = counNodes; int idx3 = 0; iot.jcypher.query.api.predicate.Concatenator booleanOp = null; JcNode nd = APIAccess.getNodeForType(intersectionDom, clazz); String nnm = ValueAccess.getName(nd).concat(collNodePostPrefix) .concat(APIAccess.getBaseNodeName(cpt.domainObjectMatch)); nd = new JcNode(nnm); if (ret == null) ret = new ArrayList<JcNode>(); ret.add(nd); clauses.add(OPTIONAL_MATCH.node(nd)); for (ClausesPerType intCpt : buildIntersection) { JcNode n = intCpt.node; if (idx3 == 0) booleanOp = WHERE.BR_OPEN().NOT().valueOf(n).IS_NULL().AND().valueOf(nd).IN_list(n).BR_CLOSE(); else booleanOp = booleanOp.AND().BR_OPEN().NOT().valueOf(n).IS_NULL().AND().valueOf(nd).IN_list(n).BR_CLOSE(); idx3++; } //booleanOp = booleanOp.BR_CLOSE(); clauses.add(booleanOp); addCountWithClause(nd, countWithClauses, null, clauses, cpt, false); return ret; } private void handleBrackets(ClausesPerType cpt, ClauseBuilderContext cbContext, List<IASTObject> pendingBr) { if (!pendingBr.isEmpty()) { for (IASTObject iao : pendingBr) addClause(iao, cpt, cbContext); pendingBr.clear(); } } private int handleScopeClose(int scope, int newScope, ClausesPerType cpt, ClauseBuilderContext cbContext, List<IASTObject> pendingBrClose) { if (scope != -1 && scope != newScope) { if (!pendingBrClose.isEmpty()) { for (IASTObject iao : pendingBrClose) addClause(iao, cpt, cbContext); pendingBrClose.clear(); } else addClause(new ConcatenateExpression(Concatenator.BR_CLOSE), cpt, cbContext); } return newScope; } private IASTObject handleScopeOpen(int scope, int newScope, ClausesPerType cpt, ClauseBuilderContext cbContext, List<IASTObject> pendingBrOpen, IASTObject pendingOr) { if (scope != newScope) { if (pendingOr != null) addClause(pendingOr, cpt, cbContext); if (!pendingBrOpen.isEmpty()) { for (IASTObject iao : pendingBrOpen) addClause(iao, cpt, cbContext); pendingBrOpen.clear(); } else addClause(new ConcatenateExpression(Concatenator.BR_OPEN), cpt, cbContext); return null; } else return pendingOr; } private void addCountWithClause(JcNode n, List<IClause> countWithClauses, List<IClause> selfWithClauses, List<IClause> selectClauses, ClausesPerType cpt, boolean addSelf) { String nnm = ValueAccess.getName(n); String alias = nnm.concat(countXprPostPrefix); JcNumber num = new JcNumber(alias); selectClauses.add(WITH.count().DISTINCT().value(n).AS(num)); if (addSelf) selectClauses.add(WITH.value(n)); selectClauses.addAll(countWithClauses); if (addSelf) selectClauses.addAll(selfWithClauses); if (cpt.withClausesAddIdxs == null) cpt.withClausesAddIdxs = new ArrayList<Integer>(); cpt.withClausesAddIdxs.add(0, selectClauses.size()); countWithClauses.add(0, WITH.value(num)); if (addSelf) selfWithClauses.add(WITH.value(n)); } private void addTraversalConstraints2Select(TraversalPathsPerDOM tpd, List<JcNode> startNds, ClausesPerType cpt) { List<JcPath> paths = tpd.getAllPaths(startNds); if (paths.size() > 0) { cpt.oneValid = true; boolean bracket = false; if (cpt.concatenator != null) { cpt.concat = cpt.concatenator.AND().BR_OPEN(); bracket = true; } int idx = 0; for(JcPath path : paths) { if (idx > 0) cpt.concatenator = cpt.concatenator.OR().valueOf(cpt.node).EQUALS(path.nodes().head()); else cpt.concatenator = cpt.concat.valueOf(cpt.node).EQUALS(path.nodes().head()); idx++; } if (bracket) cpt.concatenator = cpt.concatenator.BR_CLOSE(); } } private List<StepClause> buildStepClauses(TraversalExpression travEx, int tmpNodeIdx, String startNodeName, String endNodeLabel, String origEndNodeName, boolean copy) { StepClause stepClause = new StepClause(); stepClause.buildAll(travEx, tmpNodeIdx, startNodeName, endNodeLabel, origEndNodeName, copy); return this.filterValidClauses(stepClause.stepClauses); } private List<StepClause> filterValidClauses(List<StepClause> toFilter) { List<StepClause> ret = new ArrayList<StepClause>(); for(StepClause sc : toFilter) { if (sc.getTotalMatchNode() != null) { boolean doAdd = true; for (StepClause scRet : ret) { if (scRet.isSameAs(sc)) { doAdd = false; break; } } if (doAdd) ret.add(sc); } } return ret; } private String pathFromNode(String nodeName) { return nodeName.replaceFirst(APIAccess.nodePrefix, APIAccess.pathPrefix); } /***************************/ private class CountIntersectsPerType { private UnionExpression intersection; private List<ClausesPerType> buildIntersection; private DomainObjectMatch<?> intersectionDom; private List<JcNode> countNodes; } /***************************/ private class TraversalPathsPerDOM { // key is the start node name private Map<String, List<JcPath>> pathMap; private boolean consumed; private TraversalPathsPerDOM() { super(); this.pathMap = new HashMap<String, List<JcPath>>(); this.consumed = false; } private void addAll(Map<String, List<JcPath>> pMap) { Iterator<Entry<String, List<JcPath>>> it = pMap.entrySet().iterator(); while(it.hasNext()) { Entry<String, List<JcPath>> entry = it.next(); List<JcPath> pths = this.pathMap.get(entry.getKey()); if (pths == null) { pths = new ArrayList<JcPath>(); this.pathMap.put(entry.getKey(), pths); } pths.addAll(entry.getValue()); } } private List<JcPath> getAllPaths(List<JcNode> nodes) { List<JcPath> ret = new ArrayList<JcPath>(); for (JcNode n : nodes) { List<JcPath> pths = this.pathMap.get(ValueAccess.getName(n)); if (pths != null) ret.addAll(pths); } return ret; } } /***************************/ private class TraversalResult { private List<List<IClause>> expressionParts; // key is the start node name private Map<String, List<JcPath>> startPathMap; private List<ClausesPerType> clausesPerTypeList; private ExpressionsPerDOM expressionsPerDOM; private ExpressionsPerDOM countExpressionsPerDOM; private Map<Class<?>, CountIntersectsPerType> countIntersectsPerType; private List<IClause> getAllClauses() { List<IClause> ret = new ArrayList<IClause>(); for (List<IClause> cls : this.expressionParts) { ret.addAll(cls); } return ret; } private Map<String, List<JcPath>> getStartPathMap() { if (this.startPathMap == null) this.startPathMap = new HashMap<String, List<JcPath>>(); return this.startPathMap; } private void addStartPath(String startNodeName, JcPath startPath) { List<JcPath> pths = this.getStartPathMap().get(startNodeName); if (pths == null) { pths = new ArrayList<JcPath>(); this.getStartPathMap().put(startNodeName, pths); } pths.add(startPath); } private List<ClausesPerType> getClausesPerTypeList() { if (this.clausesPerTypeList == null) this.clausesPerTypeList = new ArrayList<ClausesPerType>(); return this.clausesPerTypeList; } } /***************************/ private class StepClause { private Node matchNode; private StepClause next; private StepClause previous; private List<FieldMapping> fieldMappings; private int fmIndex; private TraversalExpression traversalExpression; private Step step; private int stepIndex; private String originalEndNodeName; private String endNodeLabel; private JcPath jcPath; private JcNode startNode; private JcNode endNode; private List<StepClause> stepClauses; private void buildAll(TraversalExpression travEx, int tmpNodeIdx, String startNodeName, String endNodeLabel, String origEndNodeName, boolean copy) { this.stepClauses = new ArrayList<StepClause>(); this.originalEndNodeName = origEndNodeName; this.endNodeLabel = endNodeLabel; this.traversalExpression = travEx; this.buildFirst(tmpNodeIdx, startNodeName, copy); } private void buildFirst(int tmpNodeIdx, String startNodeName, boolean copy) { this.stepClauses.add(this); this.startNode = new JcNode(startNodeName); if (copy) { this.jcPath = new JcPath(pathFromNode(this.originalEndNodeName)); this.matchNode = OPTIONAL_MATCH.path(this.jcPath).node(this.startNode); } else this.matchNode = OPTIONAL_MATCH.node(this.startNode); Class<?> typ = APIAccess.getTypeForNodeName(this.traversalExpression.getStart(), startNodeName); boolean isList = typ.equals(Collection.class) || typ.equals(Array.class); // TODO what about other surrogates this.stepIndex = 0; this.step = this.traversalExpression.getSteps().get(this.stepIndex); List<Class<?>> types = new ArrayList<Class<?>>(); types.add(typ); this.build(tmpNodeIdx, types, isList); } private void buildNext(int tmpNodeIdx, List<Class<?>> types, boolean isList, Step nextStep, int nextStepIndex) { StepClause stpc = new StepClause(); this.next = stpc; stpc.previous = this; stpc.matchNode = this.matchNode; stpc.endNodeLabel = this.endNodeLabel; stpc.originalEndNodeName = this.originalEndNodeName; stpc.traversalExpression = this.traversalExpression; stpc.step = nextStep; stpc.stepClauses = this.stepClauses; stpc.stepIndex = nextStepIndex; stpc.build(tmpNodeIdx, types, isList); } private void buildNextClone(int tmpNodeIdx, List<Class<?>> types, boolean isList, CloneInfo cloneInfo) { cloneInfo.toClone = cloneInfo.toClone.next; StepClause stpc = new StepClause(); this.next = stpc; stpc.previous = this; stpc.matchNode = this.matchNode; stpc.endNodeLabel = this.endNodeLabel; stpc.originalEndNodeName = this.originalEndNodeName; stpc.traversalExpression = this.traversalExpression; stpc.stepClauses = this.stepClauses; stpc.stepIndex = cloneInfo.toClone.stepIndex; stpc.step = cloneInfo.toClone.step; stpc.fieldMappings = cloneInfo.toClone.fieldMappings; stpc.fmIndex = cloneInfo.toClone.fmIndex; stpc.buildClone(tmpNodeIdx, cloneInfo, isList); } private void build(int tmpNodeIdx, List<Class<?>> types, boolean isList) { List<FieldMapping> fms = null; for (Class<?> t : types) { if (this.step.getDirection() == 0) { // forward FieldMapping fm = getMappingInfo().getFieldMapping(this.step.getAttributeName(), t); if (fm != null) { if (fms == null) fms = new ArrayList<FieldMapping>(); fms.add(fm); } } else { // backward if (fms == null) fms = new ArrayList<FieldMapping>(); List<FieldMapping> fms_t = getMappingInfo().getBackwardFieldMappings(this.step.getAttributeName(), t); for (FieldMapping f : fms_t) { if (!fms.contains(f)) fms.add(f); } } } boolean doBuild = false; if (fms != null && !fms.isEmpty()) { this.fieldMappings = fms; this.fmIndex = 0; doBuild = true; } else { // navigation has no result this.matchNode = null; } if (doBuild) { if (this.step.getDirection() == 0) // forward this.buildStep(tmpNodeIdx, isList, null, true); else this.buildStep(tmpNodeIdx, isList, null, false); } // test for additional paths if (this.fieldMappings != null && this.fmIndex < this.fieldMappings.size() - 1) { this.cloneTraversal(this, tmpNodeIdx + 1); } } private void buildStep(int tmpNodeIdx, boolean listOrArray, CloneInfo cloneInfo, boolean forward) { FieldMapping fm = this.fieldMappings.get(this.fmIndex); Relation matchRel = matchNode.relation().type(fm.getPropertyOrRelationName()); if (forward) matchRel = matchRel.out(); else matchRel = matchRel.in(); if (this.step.getMinDistance() != 1) matchRel = matchRel.minHops(this.step.getMinDistance()); if (this.step.getMaxDistance() != 1) { if (this.step.getMaxDistance() == -1) matchRel = matchRel.maxHopsUnbound(); else matchRel = matchRel.maxHops(this.step.getMaxDistance()); } Class<?> typ; CompoundObjectType cType = null; if (forward) { if (listOrArray) { cType = getMappingInfo().getInternalDomainAccess() .getFieldComponentType(fm.getClassFieldName()); } else { cType = getMappingInfo().getInternalDomainAccess() .getConcreteFieldType(fm.getClassFieldName()); } if (cType != null) typ = cType.getType(); else typ = fm.getFieldType(); } else { // backward typ = fm.getField().getDeclaringClass(); } boolean isList = typ.equals(Collection.class) || typ.equals(Array.class); // TODO alternative check for surrogate Step nextStep = null; int nextStepIndex = this.stepIndex; List<Class<?>> types; if (isList) { // need to advance one step // surrogates have one (non-transient) field if (forward) { nextStep = this.step.createStep(this.step.getDirection(), getMappingInfo().getObjectMappingFor(typ) .fieldMappingsIterator().next().getPropertyOrRelationName()); } else { // backward nextStep = this.step.createStep(this.step.getDirection(), this.step.getAttributeName()); } types = new ArrayList<Class<?>>(); types.add(typ); } else { nextStepIndex++; if (nextStepIndex <= this.traversalExpression.getSteps().size() - 1) nextStep = this.traversalExpression.getSteps().get(nextStepIndex); if (forward) { if (cType != null) types = cType.getTypes(true); else if (CompoundObjectType.isConcrete(typ)) { types = new ArrayList<Class<?>>(); types.add(typ); } else types = new ArrayList<Class<?>>(); } else types = getMappingInfo().getCompoundTypesFor(typ); } if (nextStep == null) { // we have reached the end if (isValidEndNodeType(types)) { this.endNode = new JcNode(this.originalEndNodeName.concat(tmpNodePostPrefix) .concat(String.valueOf(tmpNodeIdx))); StepClause first = this.getFirst(); if (first.jcPath != null) { String npm = ValueAccess.getName(first.jcPath).concat(tmpNodePostPrefix) .concat(String.valueOf(tmpNodeIdx)); ValueAccess.setName(npm, first.jcPath); } this.matchNode = matchRel.node(this.endNode).label(this.endNodeLabel); } else this.matchNode = null; } else { this.matchNode = matchRel.node(); if (cloneInfo != null) this.buildNextClone(tmpNodeIdx, types, isList, cloneInfo); else this.buildNext(tmpNodeIdx, types, isList, nextStep, nextStepIndex); } } private boolean isValidEndNodeType(List<Class<?>> types) { for (Class<?> clazz : types) { if (this.endNodeLabel.equals(getMappingInfo().getLabelForClass(clazz))) return true; } return false; } private void cloneTraversal(StepClause cloneTo, int tmpNodeIdx) { StepClause first = this.getFirst(); StepClause newFirst = new StepClause(); newFirst.stepClauses = first.stepClauses; CloneInfo cloneInfo = new CloneInfo(); cloneInfo.toClone = first; cloneInfo.cloneTo = cloneTo; newFirst.cloneFirst(cloneInfo, tmpNodeIdx); } private void cloneFirst(CloneInfo cloneInfo, int tmpNodeIdx) { this.stepClauses.add(this); this.startNode = cloneInfo.toClone.startNode; this.jcPath = cloneInfo.toClone.jcPath; this.originalEndNodeName = cloneInfo.toClone.originalEndNodeName; this.endNodeLabel = cloneInfo.toClone.endNodeLabel; this.traversalExpression = cloneInfo.toClone.traversalExpression; if (this.jcPath != null) this.matchNode = OPTIONAL_MATCH.path(this.jcPath).node(this.startNode); else this.matchNode = OPTIONAL_MATCH.node(this.startNode); Class<?> typ = APIAccess.getTypeForNodeName(this.traversalExpression.getStart(), ValueAccess.getName(this.startNode)); boolean isList = typ.equals(Collection.class) || typ.equals(Array.class); // TODO what about other surrogates this.stepIndex = cloneInfo.toClone.stepIndex; this.step = cloneInfo.toClone.step; this.fieldMappings = cloneInfo.toClone.fieldMappings; this.fmIndex = cloneInfo.toClone.fmIndex; this.buildClone(tmpNodeIdx, cloneInfo, isList); } private void buildClone(int tmpNodeIdx, CloneInfo cloneInfo, boolean isList) { CloneInfo cli = cloneInfo; if (cli.stopCloning()) { this.fmIndex++; cli = null; } if (this.step.getDirection() == 0) // forward this.buildStep(tmpNodeIdx, isList, cli, true); else this.buildStep(tmpNodeIdx, isList, cli, false); // test for additional paths if (this.fieldMappings != null && this.fmIndex < this.fieldMappings.size() - 1) { this.cloneTraversal(this, tmpNodeIdx + 1); } } private JcNode getEndNode() { return this.getLast().endNode; } private Node getTotalMatchNode() { return this.getLast().matchNode; } private StepClause getFirst() { StepClause first = this; while(first.previous != null) { first = first.previous; } return first; } private StepClause getLast() { StepClause last = this; while(last.next != null) { last = last.next; } return last; } private boolean isSameAs(StepClause toCompare) { if (toCompare == null) return false; if (!this.originalEndNodeName.equals(toCompare.originalEndNodeName)) return false; if (this.startNode != null) { if (toCompare.startNode == null) return false; if (!ValueAccess.getName(this.startNode).equals( ValueAccess.getName(toCompare.startNode))) return false; } else if (toCompare.startNode != null) return false; String relName = getFieldMapping().getPropertyOrRelationName(); String oRelName = toCompare.getFieldMapping().getPropertyOrRelationName(); if (!relName.equals(oRelName)) return false; if (this.next != null) return this.next.isSameAs(toCompare.next); else if (toCompare.next != null) return false; return true; } private FieldMapping getFieldMapping() { if (this.fieldMappings != null) return this.fieldMappings.get(this.fmIndex); return null; } } /***********************************/ private class CloneInfo { private StepClause toClone; private StepClause cloneTo; private boolean stopCloning() { return this.toClone == this.cloneTo; } } } /*************************************/ private class ClauseBuilderContext { private int stepClausesCount = 0; private List<String> validNodes; // The DomainObjectMatch as Map-Key is owner of the collection expression; // the one which is produced by the collection expression. // The Class which is key in the inner map is one type of the DomainObjectMatch which is owner // of the tarversal expression (produced by the traversal expression) // collection owner -> traversal owner -> TraversalResultsPerType private Map<DomainObjectMatch<?>, Map<DomainObjectMatch<?>, TraversalResultPerTypes>> travClausesPerCollectXpr; private ClauseBuilderContext(List<String> validNodes) { super(); this.validNodes = validNodes; } private void addCountFor(DomainObjectMatch<?> collOwner, DomainObjectMatch<?> travOwner, String startNodeName, JcNode endNd) { TraversalResultPerTypes travResPerTypes = this.getTraversalResultPerTypes(collOwner, travOwner, true); travResPerTypes.addCount(startNodeName, endNd); } private List<JcNode> getCountsFor(DomainObjectMatch<?> collOwner, DomainObjectMatch<?> travOwner, String startNodeName) { UnionExpression ue; TraversalResultPerTypes travResPerTypes; if ((ue = APIAccess.getUnionExpression(travOwner)) != null) { List<JcNode> ret = new ArrayList<JcNode>(); for (DomainObjectMatch<?> dom : ue.getSources()) { travResPerTypes = this.getTraversalResultPerTypes(collOwner, dom, false); if (travResPerTypes != null) { List<JcNode> cf = travResPerTypes.getCountsFor(startNodeName); if (cf != null) ret.addAll(travResPerTypes.getCountsFor(startNodeName)); } } return ret; } travResPerTypes = this.getTraversalResultPerTypes(collOwner, travOwner, false); if (travResPerTypes != null) return travResPerTypes.getCountsFor(startNodeName); return null; } private void addTravClausesPerColl(DomainObjectMatch<?> collOwner, DomainObjectMatch<?> travOwner, Class<?> travOwnerType, ClauseBuilder.TraversalResult travResult) { TraversalResultPerTypes travResPerTypes = this.getTraversalResultPerTypes(collOwner, travOwner, true); travResPerTypes.addTravResult(travOwnerType, travResult); } private TraversalResultPerTypes getTraversalResultPerTypes(DomainObjectMatch<?> collOwner, DomainObjectMatch<?> travOwner, boolean doCreate) { if (this.travClausesPerCollectXpr == null) { if (!doCreate) return null; this.travClausesPerCollectXpr = new HashMap<DomainObjectMatch<?>, Map<DomainObjectMatch<?>, TraversalResultPerTypes>>(); } Map<DomainObjectMatch<?>, TraversalResultPerTypes> travResOwnerMap = this.travClausesPerCollectXpr.get(collOwner); if (travResOwnerMap == null) { if (!doCreate) return null; travResOwnerMap = new HashMap<DomainObjectMatch<?>, TraversalResultPerTypes>(); this.travClausesPerCollectXpr.put(collOwner, travResOwnerMap); } TraversalResultPerTypes travResPerTypes = travResOwnerMap.get(travOwner); if (travResPerTypes == null) { if (!doCreate) return null; travResPerTypes = new TraversalResultPerTypes(); travResOwnerMap.put(travOwner, travResPerTypes); } return travResPerTypes; } private ClauseBuilder.TraversalResult getTravResult(DomainObjectMatch<?> collOwner, DomainObjectMatch<?> travOwner, Class<?> travOwnerType) { TraversalResultPerTypes travResPerTypes = this.getTraversalResultPerTypes(collOwner, travOwner, false); if (travResPerTypes != null) return travResPerTypes.getTravResult(travOwnerType); return null; } private boolean isNodeValid(JcNode nd) { String ndName = ValueAccess.getName(nd); for (String n : this.validNodes) { if (n.equals(ndName)) return true; } return false; } private List<JcNode> filterValidNodes(List<JcNode> nds) { List<JcNode> ret = new ArrayList<JcNode>(); for (JcNode n : nds) { if (this.isNodeValid(n)) ret.add(n); } return ret; } /*************************************/ private class TraversalResultPerTypes { private Map<Class<?>, ClauseBuilder.TraversalResult> travResultsPerType; // traversal start node name -> list of traversal end nodes private Map<String, List<JcNode>> startNode2CountMap; private void addCount(String startNodeName, JcNode endNode) { if (this.startNode2CountMap == null) this.startNode2CountMap = new HashMap<String, List<JcNode>>(); List<JcNode> endNodes = this.startNode2CountMap.get(startNodeName); if (endNodes == null) { endNodes = new ArrayList<JcNode>(); this.startNode2CountMap.put(startNodeName, endNodes); } endNodes.add(endNode); } private List<JcNode> getCountsFor(String startNodeName) { if (this.startNode2CountMap != null) return this.startNode2CountMap.get(startNodeName); return null; } private void addTravResult(Class<?> travOwnerType, ClauseBuilder.TraversalResult traversalresult) { if (this.travResultsPerType == null) this.travResultsPerType = new HashMap<Class<?>, ClauseBuilder.TraversalResult>(); this.travResultsPerType.put(travOwnerType, traversalresult); } private ClauseBuilder.TraversalResult getTravResult(Class<?> travOwnerType) { if (this.travResultsPerType != null) return this.travResultsPerType.get(travOwnerType); return null; } } } /*************************************/ private class StateContext { private State state; private int blockCount; private String baseNodeName; private List<IASTObject> candidates; private List<String> dependencies; // -1 .. init, // 0 .. foreign predicate expression, private int orRemoveState; private IASTObject orToAdd; private StateContext(String bndNm) { super(); this.state = State.INIT; this.blockCount = 0; this.candidates = new ArrayList<IASTObject>(); this.orRemoveState = -1; this.baseNodeName = bndNm; } private void addDependency(String nodeName) { if (nodeName != null) { String nn = nodeName; if (this.dependencies == null) this.dependencies = new ArrayList<String>(); int idx = nodeName.indexOf(separator); idx = nodeName.indexOf(separator, idx + 1); // extract base-node-name if needed if (idx >= 0) nn = nodeName.substring(0, idx); if (!nn.equals(this.baseNodeName) && !this.dependencies.contains(nn)) this.dependencies.add(nn); } } } /*************************************/ private class ClausesPerType { private DomainObjectMatch<?> domainObjectMatch; private JcNode node; private Class<?> domainObjectType; private boolean valid; private boolean oneValid; private boolean previousOr; private boolean testForNextIsOr; private int pageOffset; private int pageLength; private List<IClause> clauses; private ExpressionsPerDOM expressionsPerDOM; private Concat concat = null; private iot.jcypher.query.api.predicate.Concatenator concatenator = null; private List<IClause> traversalClauses; private List<IClause> collectionClauses; private List<Long> startIds; private boolean lastIsWith; private boolean isCollectExpression; private boolean startCountSelectXpr; private List<Integer> withClausesAddIdxs; private boolean closeBracket; private ClausesPerType(JcNode node, DomainObjectMatch<?> dom, Class<?> type) { super(); this.node = node; this.domainObjectMatch = dom; this.domainObjectType = type; this.valid = true; this.oneValid = false; this.previousOr = false; this.testForNextIsOr = false; this.closeBracket = false; this.startCountSelectXpr = false; this.isCollectExpression = false; this.lastIsWith = false; } private boolean needDistinctQuery() { return this.pageOffset > 0 || this.pageLength >= 0; } private JcNumber getJcNumber(String prefix) { return QueryBuilder.this.getJcNumber(prefix, this.node); } /** * may return null in case it is not valid * @return */ private List<IClause> getClauses(List<ClausesPerType> added) { if (this.clauses == null) { if (this.valid) { this.clauses = new ArrayList<IClause>(); if (this.traversalClauses != null) { if (this.concatenator != null) { IClause cl = this.traversalClauses.get( this.traversalClauses.size() - 1); if (cl instanceof iot.jcypher.query.api.predicate.Concatenator) this.traversalClauses.remove(this.traversalClauses.size() - 1); } this.clauses.addAll(this.traversalClauses); this.traversalClauses = null; } else if (this.collectionClauses != null) { int offset = this.clauses.size(); this.clauses.addAll(this.collectionClauses); if (this.withClausesAddIdxs != null) { // add missing with clauses for (int idx : this.withClausesAddIdxs) { for (ClausesPerType cpt : added) { this.clauses.add(idx + offset, WITH.value(cpt.node)); } } } } else { String nodeLabel = getMappingInfo().getLabelForClass(this.domainObjectType); if (this.startIds != null) { long[] sids = new long[this.startIds.size()]; for (int i = 0; i < this.startIds.size(); i++) { sids[i] = this.startIds.get(i).longValue(); } this.clauses.add( START.node(this.node).byId(sids) ); } else { this.clauses.add( OPTIONAL_MATCH.node(this.node).label(nodeLabel) ); } } if (this.concatenator != null) this.clauses.add(this.closeBracket ? this.concatenator.BR_CLOSE() : this.concatenator); else this.clauses.add(SEPARATE.nextClause()); } } return this.clauses; } private void addClausesTo(List<IClause> clauses, List<ClausesPerType> added) { List<IClause> mcs = this.getClauses(added); if (mcs != null) clauses.addAll(mcs); } private void addDependencyClauses(List<IClause> clauses, List<ClausesPerType> added, OrderClausesHolder withClausesHolder, ClauseBuilderContext cbContext, boolean isCountQuery) { if (this.expressionsPerDOM.flattenedDependencies != null) { for (ExpressionsPerDOM xpd : this.expressionsPerDOM.flattenedDependencies) { // for a traversal expression for this select expression we do not need to // add the original traversal expression, because the select expression // will work on the clones. List<DomainObjectMatch<?>> collOwner = APIAccess.getCollectExpressionOwner(xpd.domainObjectMatch); boolean addExpressions = collOwner == null || !collOwner.contains(this.domainObjectMatch); for (ClausesPerType cpt : xpd.clausesPerTypes) { if (cbContext.isNodeValid(cpt.node)) { if (!added.contains(cpt)) { if (addExpressions) { if (!isCountQuery) withClausesHolder.checkForOrdeClause(cpt); cpt.addClausesTo(clauses, added); added.add(cpt); } // added.add(cpt); } } } } } } private boolean isValidDependency(String vn, List<String> depBases) { for (String bnm : depBases) { if (vn.startsWith(bnm)) return true; } return false; } } /*************************************/ private class ExpressionsPerDOM { private DomainObjectMatch<?> domainObjectMatch; private List<IASTObject> xPressions; private List<DomainObjectMatch<?>> dependencies; private List<ExpressionsPerDOM> flattenedDependencies; private List<ClausesPerType> clausesPerTypes; private ExpressionsPerDOM(DomainObjectMatch<?> domainObjectMatch, List<IASTObject> xPressions, List<String> deps) { super(); this.domainObjectMatch = domainObjectMatch; this.xPressions = xPressions; if (deps != null) { this.dependencies = new ArrayList<DomainObjectMatch<?>>(deps.size()); for (String nn : deps) { for (DomainObjectMatch<?> dom : domainObjectMatches) { if (APIAccess.getBaseNodeName(dom).equals(nn)) { this.dependencies.add(dom); break; } } } } } private void addDependencies(List<ExpressionsPerDOM> xpds) { if (xpds != null) { if (this.flattenedDependencies == null) { this.flattenedDependencies = new ArrayList<ExpressionsPerDOM>(); this.flattenedDependencies.addAll(xpds); } else { for (ExpressionsPerDOM xpd : xpds) { if (!this.flattenedDependencies.contains(xpd)) this.flattenedDependencies.add(xpd); } } } } private void addDependency(ExpressionsPerDOM xpd) { if (this.flattenedDependencies == null) this.flattenedDependencies = new ArrayList<ExpressionsPerDOM>(); this.flattenedDependencies.add(xpd); } } } /************************************/ private class QueryContext { private List<ResultsPerType> resultsPerType; private Map<DomainObjectMatch<?>, List<ResultsPerType>> resultsMap; private boolean execCount; private QueryContext(boolean execCount) { super(); this.resultsPerType = new ArrayList<ResultsPerType>(); this.resultsMap = new HashMap<DomainObjectMatch<?>, List<ResultsPerType>>(); this.execCount = execCount; } private ResultsPerType addFor(QueryBuilder.ClausesPerType cpt, boolean isCountQuery) { DomainObjectMatch<?> dom = cpt.domainObjectMatch; Class<?> type = cpt.domainObjectType; List<ResultsPerType> resPerTypeList = this.resultsMap.get(dom); if (resPerTypeList == null) { resPerTypeList = new ArrayList<ResultsPerType>(); this.resultsMap.put(dom, resPerTypeList); } ResultsPerType resPerType = null; for (ResultsPerType rpt : resPerTypeList) { if (rpt.type.equals(type)) { resPerType = rpt; break; } } if (resPerType == null) { String pref = isCountQuery ? countPrefix : idPrefix; resPerType = new ResultsPerType(type, ValueAccess.getName(cpt.node), cpt.getJcNumber(pref)); resPerTypeList.add(resPerType); } return resPerType; } private void addEmptyFor(QueryBuilder.ClausesPerType cpt) { DomainObjectMatch<?> dom = cpt.domainObjectMatch; List<ResultsPerType> resPerTypeList = this.resultsMap.get(dom); if (resPerTypeList == null) { resPerTypeList = new ArrayList<ResultsPerType>(); this.resultsMap.put(dom, resPerTypeList); } } private void queryExecuted() { Iterator<Entry<DomainObjectMatch<?>, List<ResultsPerType>>> it = this.resultsMap.entrySet().iterator(); while(it.hasNext()) { Entry<DomainObjectMatch<?>, List<ResultsPerType>> entry = it.next(); APIAccess.setPageChanged(entry.getKey(), false); } } /************************************/ private class ResultsPerType { private Class<?> type; private JcPrimitive jcPrimitive; private JcNumber jcNumber; private List<Long> ids; private List<Object> simpleValues; private long count; private int queryIndex; private ResultsPerType(Class<?> type, String retName, JcNumber num) { super(); this.type = type; this.jcNumber = num; this.count = 0; this.queryIndex = 0; this.jcPrimitive = MappingUtil.fromType(type, retName); } } } /*************************************/ private enum State { INIT, HAS_XPRESSION } /****************************************************/ private class RecordedQueryContext { private RecordedQuery recordedQuery; private QueriesPerThread queriesPerThread; private AbstractDomainQuery domainQuery; private Map<Object, String> object2IdMap; private DomainQueryResult queryResult; private CountQueryResult countResult; private RecordedQueryContext(RecordedQuery recordedQuery, QueriesPerThread queriesPerThread, AbstractDomainQuery q) { super(); this.recordedQuery = recordedQuery; this.queriesPerThread = queriesPerThread; this.domainQuery = q; } private void queryCompleted() { if (this.queriesPerThread != null) { if (this.object2IdMap == null) this.object2IdMap = this.queriesPerThread.getDOM2IdMap(this.domainQuery); this.queriesPerThread.queryCompleted(this.domainQuery); } } private DomainObjectMatch<?> findMatchingTo(DomainObjectMatch<?> dom) { DomainObjectMatch<?> om = APIAccess.getDelegate(dom); // handle generic model if (om == null) om = dom; String oid = this.object2IdMap.get(om); ReplayedQueryContext rctxt = InternalAccess.getQueryExecutor(getReplayedQuery()).getReplayedQueryContext(); DomainObjectMatch<?> match = rctxt.getById(oid); return match; } private AbstractDomainQuery getReplayedQuery() { if (this.queryResult != null) return InternalAccess.getDomainQuery(this.queryResult); else if (this.countResult != null) return InternalAccess.getDomainQuery(this.countResult); return null; } } /************************************/ public class MappingInfo { private MappingInfo() { super(); } public ObjectMapping getObjectMappingFor(Class<?> domainObjectType) { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess() .getObjectMappingFor(domainObjectType); } /** * may return null * @param attribName * @param domainObjectType * @return */ public FieldMapping getFieldMapping(String attribName, Class<?> domainObjectType) { return getObjectMappingFor(domainObjectType).getFieldMappingForField(attribName); } public List<FieldMapping> getBackwardFieldMappings(String attribName, Class<?> domainObjectType) { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess() .getBackwardFieldMappings(attribName, domainObjectType); } public List<Class<?>> getCompoundTypesFor(Class<?> domainObjectType) { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess() .getCompoundTypesFor(domainObjectType); } public String getLabelForClass(Class<?> clazz) { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess() .getLabelForClass(clazz); } public Class<?> getClassForLabel(String label) { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess() .getClassForLabel(label); } public QueryExecutor getQueryExecutor() { return QueryExecutor.this; } public InternalDomainAccess getInternalDomainAccess() { return ((IIntDomainAccess)domainAccess).getInternalDomainAccess(); } } }