/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.reasoner.rulesys.impl; import java.util.*; import org.apache.jena.reasoner.TriplePattern ; /** * A generator represents a set of memoized results for a single * tabled subgoal. The generator may be complete (in which case it just * contains the complete cached set of results for a goal), ready (not complete * but likely to product more results if called) or blocked (not complete and * awaiting results from a dependent generator). * <p> * Each generator may have multiple associated consumer choice points * representing different choices in satisfying the generator's goal. * </p> */ public class Generator implements LPAgendaEntry, LPInterpreterContext { /** The intepreter instance which generates the results for this goal, * null if the generator is complete */ protected LPInterpreter interpreter; /** The ordered set of results available for the goal */ protected ArrayList<Object> results = new ArrayList<>(); /** A indexed version of the result set, used while the generator is live * to detect duplicate results */ protected Set<Object> resultSet; /** set to true if the dependent generator has new results ready for us */ protected boolean isReady = true; /** set to true if at least one branch has block so an active readiness check is required */ protected boolean checkReadyNeeded = false; /** The set of choice points producing results for us to use */ protected Set<ConsumerChoicePointFrame> generatingCPs = new HashSet<>(); /** The list of active consumer choice points consuming results from this generator */ protected Set<ConsumerChoicePointFrame> consumingCPs = new HashSet<>(); /** Flags whether the generator is live/dead/unknown during completion checking */ protected LFlag completionState; /** The goal the generator is satisfying - just used in debugging */ protected TriplePattern goal; /** True if this generator can produce at most one answer */ protected boolean isSingleton; // /** Distance of generator from top level goal, used in scheduling */ // protected int depth = DEFAULT_DEPTH; // // /** Default depth if not known */ // public static final int DEFAULT_DEPTH = 100; /** * Constructor. * * @param interpreter an initialized interpreter instance that will answer * results for this generator. */ public Generator(LPInterpreter interpreter, TriplePattern goal) { this.interpreter = interpreter; this.goal = goal; // Just used for debugging isSingleton = goal.isGround(); if (!isSingleton) resultSet = new HashSet<>(); } /** * Return the number of results available from this context. */ public int numResults() { return results.size(); } /** * Return true if the generator is ready to be scheduled (i.e. it is not * known to be complete and not known to be waiting for a dependent generator). */ @Override public boolean isReady() { if (isComplete()) return false; if (checkReadyNeeded) { isReady = false; for ( ConsumerChoicePointFrame generatingCP : generatingCPs ) { if ( generatingCP.isReady() ) { isReady = true; break; } } checkReadyNeeded = false; return isReady; } else { return isReady; } } /** * Directly set that this generator is ready (because the generator * for one of its generatingCPs has produced new results). */ @Override public void setReady(ConsumerChoicePointFrame ccp) { if (!isComplete()) { interpreter.engine.schedule(ccp); isReady = true; checkReadyNeeded = false; } } /** * Return true if the generator is complete. */ public boolean isComplete() { return interpreter == null; } // /** // * Return the estimated number of generators between the top level goal and this one. // */ // public int getDepth() { // return depth; // } // /** // * Set the depth of this generator, it will not be propagated until // * a further depednents are found. // */ // public void setDepth(int d) { // depth = d; // } /** * Signal that this generator is complete, no more results can be created. */ public void setComplete() { if (!isComplete()) { interpreter.close(); interpreter = null; resultSet = null; isReady = false; completionState = LFlag.DEAD; // Anyone we were generating results for is now finished for ( ConsumerChoicePointFrame ccp : consumingCPs ) { if ( !ccp.isReady() ) { ccp.setFinished(); } } generatingCPs = null; consumingCPs.clear(); } } /** * Add a new client choince point to consume results from this generator. */ public void addConsumer(ConsumerChoicePointFrame ccp) { consumingCPs.add(ccp); // // Update distance from top goal // int newDepth = ccp.context == null ? 1 : ccp.context.getDepth() + 1; // if (newDepth < depth) depth = newDepth; } /** * Remove a terminated consuming choice point from the state set. */ public void removeConsumer(ConsumerChoicePointFrame ccp) { consumingCPs.remove(ccp); // We used to set it complete if there were no consumers left. // However, a generator might be part of one query, incompletely consumed // and then opened again on a different query, // it seems better to omit this. TODO review // if (!isComplete() &&consumingCPs.isEmpty()) { // setComplete(); // } } /** * Signal dependents that we have new results. */ public void notifyResults() { for ( ConsumerChoicePointFrame cons : consumingCPs ) { cons.setReady(); } } /** * Notify that the interpreter has now blocked on the given choice point. */ @Override public void notifyBlockedOn(ConsumerChoicePointFrame ccp) { generatingCPs.add(ccp); checkReadyNeeded = true; } /** * Notify this context that the given choice point has terminated * and can be remove from the wait list. */ @Override public void notifyFinished(ConsumerChoicePointFrame ccp) { if (generatingCPs != null) { generatingCPs.remove(ccp); } checkReadyNeeded = true; } /** * Start this generator running for the first time. * Should be called from within an appropriately synchronized block. */ @Override public void pump() { pump(this); } /** * Start this generator running from the given previous blocked generating * choice point. * Should be called from within an appropriately synchronized block. */ public void pump(LPInterpreterState context) { if (isComplete()) return; interpreter.setState(context); int priorNresults = results.size(); while (true) { Object result = interpreter.next(); if (result == StateFlag.FAIL) { checkReadyNeeded = true; break; } else { // Simple triple result if (isSingleton) { results.add(result); isReady = false; break; } else if (resultSet.add(result)) { results.add(result); } } } if (results.size() > priorNresults) { notifyResults(); } // Early termination check, close a singleton as soon as we have the ans if (isSingleton && results.size() == 1) { setComplete(); } if (LPBRuleEngine.CYCLES_BETWEEN_COMPLETION_CHECK == 0) { checkForCompletions(); } } /** * Return the generator associated with this entry (might be the entry itself) */ @Override public Generator getGenerator() { return this; } /** * Check for deadlocked states where none of the generators we are (indirectly) * dependent on can run. */ public void checkForCompletions() { HashSet<Generator> visited = new HashSet<>(); if (runCompletionCheck(visited) != LFlag.LIVE) { postCompletionCheckScan(visited); } } /** * Check for deadlocked states across a collection of generators which have * been run. */ public static void checkForCompletions(Collection<? extends Generator> completions) { HashSet<Generator> visited = new HashSet<>(); boolean atLeastOneZombie = false; for ( Generator g : completions ) { if ( g.runCompletionCheck( visited ) != LFlag.LIVE ) { atLeastOneZombie = true; } } if (atLeastOneZombie) { postCompletionCheckScan(visited); } } /** * Check whether this generator is live (indirectly dependent on a ready * generator), dead (complete) or in a deadlock loop which might or * might not be live (unknown). */ protected LFlag runCompletionCheck(Set<Generator> visited) { if (isComplete()) return LFlag.DEAD; if (! visited.add(this)) return this.completionState; completionState = LFlag.UNKNOWN; if (isReady()) { completionState = LFlag.LIVE; } else { for ( ConsumerChoicePointFrame ccp : generatingCPs ) { if ( ccp.isReady() ) { completionState = LFlag.LIVE; break; } else if ( ccp.generator.runCompletionCheck( visited ) == LFlag.LIVE ) { completionState = LFlag.LIVE; break; } } } return completionState; } /** * Scan the result of a (set of) completion check(s) to detect which of the * unknowns are actually live and set the remaining (deadlocked) states * to complete. */ protected static void postCompletionCheckScan(Set<Generator> visited ) { for ( Generator g : visited ) { if ( g.completionState == LFlag.LIVE ) { for ( Iterator<ConsumerChoicePointFrame> i = g.consumingCPs.iterator(); i.hasNext(); ) { LPInterpreterContext link = i.next().getConsumingContext(); if ( link instanceof Generator ) { ( (Generator) link ).propagateLive( visited ); } } } } for ( Generator g : visited ) { if ( g.completionState != LFlag.LIVE ) { g.setComplete(); } } return; } /** * Propagate liveness state forward to consuming generators, but only those * within the filter set. */ protected void propagateLive(Set<Generator> filter) { if (completionState != LFlag.LIVE) { completionState = LFlag.LIVE; for ( ConsumerChoicePointFrame consumingCP : consumingCPs ) { LPInterpreterContext link = consumingCP.getConsumingContext(); if ( link instanceof Generator ) { ( (Generator) link ).propagateLive( filter ); } } } } /** * Inner class used to flag generator states during completeness check. */ private static class LFlag { /** Label for printing */ private String label; public static final LFlag LIVE = new LFlag("Live"); public static final LFlag DEAD = new LFlag("Dead"); public static final LFlag UNKNOWN = new LFlag("Unknown"); /** Constructor */ private LFlag(String label) { this.label = label; } /** Print string */ @Override public String toString() { return label; } } }