diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fc995e0f..9b59412975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [0.15.0-SNAPSHOT] - Unreleased - -[Full changelog](https://github.com/LearnLib/learnlib/compare/learnlib-0.14.0...HEAD) +## [0.16.0-SNAPSHOT] - Unreleased ### Added +* A `StateLocalInputSULSymbolQueryOracle` to wrap a `StateLocalInputSUL`in a `SymbolQueryOracle` (e.g. to learn partial systems with the ADT learner). +* Added an example for parallel setups (`ParallelismExample1`). + +### Changed + +* The `SULSymbolQueryOracle` now better handles the `pre` and `post` cycles of a `SUL` (e.g. calls to the `reset` method now allow to close the embedded `SUL` from outside). +* Reworked parallel oracles + * Several `SULOracle` variants are no longer thread-safe. This reduces overhead for scenarios where no parallelism is required. + * The `ParallelOracleBuilders` factory now offers builder methods for `SUL`s, `ObservableSUL`s, `StateLocalInputSUL`s, `MembershipOracles`s and `OmegaMembershipOracle`s to allow an easy (and correct) construction of parallel setups given one of the mentioned implementations. +* Refactored the following packages/classes: + * `de.learnlib.oracle.parallelism.ParallelOracleInterruptedException` -> `de.learnlib.api.oracle.parallelism.BatchInterruptedException` +* The `initialPrefixes` and `initialSuffixes` methods of `AbstractExtensibleAutomatonLStar` are now `final` since these values can be provided via the constructor of the class. This allows one to simplify sub-classes. + +### Removed + +* Removed the `learnlib.queries.parallel.threshold` property. Learning setups that want to use parallelism now need to explicitly setup parallel oracles. +* Removed `MQUtil#answerQueries{Auto,Parallel}` and `MQUtil#answerOmegaQueries{Auto,Parallel}`) +* `LassoOracle#isOmegaCounterExample(boolean)` has been removed. This decision can be directly integrated into the `#findCounterExample` method which has more information available. +* Updated to [AutomataLib 0.10.0](https://github.com/LearnLib/automatalib/releases/tag/automatalib-0.10.0) + +### Fixed + +* Fixed a bug where NL* would create non-canonical hypotheses ([#70](https://github.com/LearnLib/learnlib/issues/70), thanks to [Joshua Moerman](https://github.com/Jaxan)) + + +## [0.15.0](https://github.com/LearnLib/learnlib/releases/tag/learnlib-0.15.0) - 2020-02-06 + +[Full changelog](https://github.com/LearnLib/learnlib/compare/learnlib-0.14.0...learnlib-0.15.0) + ### Changed * The `{DFA,Mealy,}W{p,}MethodEQOracle(MembershipOracle, int, int)` constructor no longer interprets its second `int` parameter as the batch size, but as an estimate for the expected SUL size. In order to explicitly set the batch size of the oracle, use the `{DFA,Mealy,}W{p,}MethodEQOracle(MembershipOracle, int, int, int)` constructor. Now, the two parameters `lookahead` and `expectedSize` will determine the length of the *middle part* via `Math.max(lookahead, expectedSize - hypothesis.size())`. This allows to dynamically adjust the length of the *middle part* throughout the learning process. See [LearnLib/automatalib#32](https://github.com/LearnLib/automatalib/issues/32). @@ -17,7 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * LearnLib (incl. AutomataLib) no longer has a (runtime-) dependency on JSR305 (and other `javax.*`) annotations or includes them in the distribution artifact. This now makes LearnLib (incl. AutomataLib) compliant with [Oracle's binary code license](https://www.oracle.com/downloads/licenses/binary-code-license.html) and allows LearnLib (incl. AutomataLib) artifacts as-is to be bundled in binary distributions with Oracle's JDKs/JREs. * A lot of code for inferring partial Mealy machines (esp. `PartialLStarMealy` and `PartialObservationTable`) has been removed/refactored. The concept of state local inputs is now implemented as a SUL filter and introduces a special `StateLocalInputSULOracle` which early-answers queries that would traverse unavailable inputs with a previously specified symbol. This way, queries that would traverse undefined input symbols still won't be executed on the SUL but the SUL appears as a 'total' Mealy system to the learner, allowing one to use every currently existing Mealy learner as-is. See the in-tree examples for more information. * `SULCache` no longer implements `MembershipOracle`. -* Switched to [AutomataLib 0.9.0](https://github.com/LearnLib/automatalib/releases/tag/automatalib-0.9.0) +* Updated to [AutomataLib 0.9.0](https://github.com/LearnLib/automatalib/releases/tag/automatalib-0.9.0) ### Removed diff --git a/algorithms/active/adt/pom.xml b/algorithms/active/adt/pom.xml index 4c65c9874c..06b98f0307 100644 --- a/algorithms/active/adt/pom.xml +++ b/algorithms/active/adt/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/automaton/ADTHypothesis.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/automaton/ADTHypothesis.java index 16fc24798f..7ba2a64768 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/automaton/ADTHypothesis.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/automaton/ADTHypothesis.java @@ -96,16 +96,18 @@ public O getTransitionOutput(final ADTTransition transition) { return transition.getOutput(); } - @SuppressWarnings("nullness") // hypothesis is always complete @Override public Word transformAccessSequence(final Word word) { - return this.getState(word).getAccessSequence(); + final ADTState state = this.getState(word); + assert state != null; + return state.getAccessSequence(); } - @SuppressWarnings("nullness") // hypothesis is always complete @Override public boolean isAccessSequence(final Word word) { - return this.getState(word).getAccessSequence().equals(word); + final ADTState state = this.getState(word); + assert state != null; + return state.getAccessSequence().equals(word); } } diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/config/model/extender/DefaultExtender.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/config/model/extender/DefaultExtender.java index ca39fca503..8a0604862a 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/config/model/extender/DefaultExtender.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/config/model/extender/DefaultExtender.java @@ -51,7 +51,8 @@ public ExtensionResult, I, O> computeExtension(final ADTHy final PartialTransitionAnalyzer, I> partialTransitionAnalyzer, final ADTNode, I, O> ads) { // cannot compute extension for root node - if (ads.getParent() == null) { + final ADTNode, I, O> parent = ads.getParent(); + if (parent == null) { return ExtensionResult.empty(); } @@ -62,7 +63,7 @@ public ExtensionResult, I, O> computeExtension(final ADTHy return ExtensionResult.empty(); } - final Pair, Word> parentTrace = ADTUtil.buildTraceForNode(ads.getParent()); + final Pair, Word> parentTrace = ADTUtil.buildTraceForNode(parent); // as long as we encounter exceptions, repeat while (true) { @@ -133,7 +134,7 @@ public ExtensionResult, I, O> computeExtension(final ADTHy } return new ExtensionResult<>(extension); - } catch (PartialTransitionAnalyzer.HypothesisModificationException hme) { + } catch (PartialTransitionAnalyzer.HypothesisModificationException ignored) { //do nothing, repeat } } diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/learner/ADTLearner.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/learner/ADTLearner.java index 0edc19eb5c..1980a12467 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/learner/ADTLearner.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/learner/ADTLearner.java @@ -197,6 +197,9 @@ public boolean refineHypothesisInternal(DefaultQuery> ceQuery) { final ADTState uState = this.hypothesis.getState(u); final ADTState uaState = this.hypothesis.getState(ua); + + assert uState != null && uaState != null; + final Word uAccessSequence = uState.getAccessSequence(); final Word uaAccessSequence = uaState.getAccessSequence(); final Word uAccessSequenceWithA = uAccessSequence.append(a); @@ -204,12 +207,15 @@ public boolean refineHypothesisInternal(DefaultQuery> ceQuery) { final ADTState newState = this.hypothesis.addState(); newState.setAccessSequence(uAccessSequenceWithA); final ADTTransition oldTrans = this.hypothesis.getTransition(uState, a); + + assert oldTrans != null; + oldTrans.setTarget(newState); oldTrans.setIsSpanningTreeEdge(true); final Set, I, O>> finalNodes = ADTUtil.collectLeaves(this.adt.getRoot()); final ADTNode, I, O> nodeToSplit = finalNodes.stream() - .filter(n -> n.getHypothesisState().equals(uaState)) + .filter(n -> uaState.equals(n.getHypothesisState())) .findFirst() .orElseThrow(IllegalStateException::new); @@ -347,6 +353,7 @@ private void closeTransition(final ADTTransition transition) { public void closeTransition(ADTState state, I input) { final ADTTransition transition = this.hypothesis.getTransition(state, input); + assert transition != null; if (transition.needsSifting()) { final ADTNode, I, O> ads = transition.getSiftNode(); @@ -364,7 +371,9 @@ public void closeTransition(ADTState state, I input) { @Override public boolean isTransitionDefined(ADTState state, I input) { - return !this.hypothesis.getTransition(state, input).needsSifting(); + final ADTTransition transition = this.hypothesis.getTransition(state, input); + assert transition != null; + return !transition.needsSifting(); } @Override @@ -745,6 +754,7 @@ private void resolveAmbiguities(final ADTNode, I, O> nodeToReplac ADTNode, I, O> oldReference = null, newReference = null; for (final ADTNode, I, O> leaf : cachedLeaves) { final ADTState hypState = leaf.getHypothesisState(); + assert hypState != null; if (hypState.equals(iter.getHypothesisState())) { oldReference = leaf; @@ -774,10 +784,12 @@ private void resolveAmbiguities(final ADTNode, I, O> nodeToReplac } final ADTNode, I, O> reset = new ADTResetNode<>(oldTrace); - final O parentOutput = ADTUtil.getOutputForSuccessor(iter.getParent(), iter); + final ADTNode, I, O> parent = iter.getParent(); + assert parent != null; + final O parentOutput = ADTUtil.getOutputForSuccessor(parent, iter); - iter.getParent().getChildren().put(parentOutput, reset); - reset.setParent(iter.getParent()); + parent.getChildren().put(parentOutput, reset); + reset.setParent(parent); oldTrace.setParent(reset); } diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/model/ObservationTree.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/model/ObservationTree.java index 18a4ff7e3f..44e51c42c3 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/model/ObservationTree.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/model/ObservationTree.java @@ -184,6 +184,7 @@ public void addState(final S newState, final Word accessSequence, final O out this.observationTree.getSuccessor(this.observationTree.getInitialState(), prefix); final FastMealyState target; + assert pred != null; if (pred.getTransitionObject(alphabet.getSymbolIndex(sym)) == null) { target = this.observationTree.addState(); this.observationTree.addTransition(pred, sym, target, output); diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/ADTUtil.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/ADTUtil.java index 186ed234ad..bc477f973a 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/ADTUtil.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/ADTUtil.java @@ -18,6 +18,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import de.learnlib.algorithms.adt.adt.ADTLeafNode; @@ -153,7 +154,7 @@ public static Pair, Word> buildTraceForNode(final ADTNode O getOutputForSuccessor(final ADTNode node, final ADTNode successor) { - if (!successor.getParent().equals(node)) { + if (!node.equals(successor.getParent())) { throw new IllegalArgumentException("No parent relationship"); } @@ -325,7 +326,7 @@ public static boolean mergeADS(final ADTNode parent, final AD while (!(ADTUtil.isLeafNode(parentIter) || ADTUtil.isResetNode(parentIter)) && !ADTUtil.isLeafNode(childIter)) { - if (!parentIter.getSymbol().equals(childIter.getSymbol())) { + if (!Objects.equals(parentIter.getSymbol(), childIter.getSymbol())) { return false; } diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/SQOOTBridge.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/SQOOTBridge.java index 8905e3c69d..833bad0fb8 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/SQOOTBridge.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/util/SQOOTBridge.java @@ -16,13 +16,14 @@ package de.learnlib.algorithms.adt.util; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; import de.learnlib.algorithms.adt.model.ObservationTree; import de.learnlib.api.oracle.SymbolQueryOracle; import net.automatalib.automata.transducers.impl.FastMealy; import net.automatalib.automata.transducers.impl.FastMealyState; -import org.checkerframework.checker.nullness.qual.Nullable; /** * A utility class that links an observation tree with a symbol query oracle, meaning that all queries to the symbol @@ -44,7 +45,7 @@ public class SQOOTBridge implements SymbolQueryOracle { private final boolean enableCache; - private final @Nullable List currentTrace; + private final List currentTrace; private FastMealyState currentState; @@ -56,7 +57,7 @@ public SQOOTBridge(final ObservationTree observationTree, this.observationTree = observationTree.getObservationTree(); this.delegate = delegate; this.enableCache = enableCache; - this.currentTrace = enableCache ? new ArrayList<>() : null; + this.currentTrace = enableCache ? new ArrayList<>() : Collections.emptyList(); } public void initialize() { @@ -98,7 +99,7 @@ public O query(I i) { this.observationTree.addTransition(this.currentState, i, newState, output); } } else { - assert output.equals(this.observationTree.getOutput(this.currentState, i)) : "Inconsistent observations"; + assert Objects.equals(output, this.observationTree.getOutput(this.currentState, i)) : "Inconsistent observations"; nextState = succ; } diff --git a/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/automaton/ADTHypothesisTest.java b/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/automaton/ADTHypothesisTest.java index f266303fa1..899975398e 100644 --- a/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/automaton/ADTHypothesisTest.java +++ b/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/automaton/ADTHypothesisTest.java @@ -46,31 +46,33 @@ public void testAutomaton() { final StateIDs> stateIds = automaton.stateIDs(); + final ADTState init = automaton.getInitialState(); + Assert.assertNotNull(init); + for (int s = 0; s < automaton.size(); s++) { for (final Character i : alphabet) { - automaton.addTransition(stateIds.getState(s), i, automaton.getInitialState(), 0); + automaton.addTransition(stateIds.getState(s), i, init, 0); } } - Assert.assertEquals(states * alphabet.size(), automaton.getInitialState().getIncomingTransitions().size()); + Assert.assertEquals(states * alphabet.size(), init.getIncomingTransitions().size()); final ADTState s1 = stateIds.getState(1), s2 = stateIds.getState(2), s3 = stateIds.getState(3); automaton.removeAllTransitions(s1); - Assert.assertEquals((states - 1) * alphabet.size(), - automaton.getInitialState().getIncomingTransitions().size()); + Assert.assertEquals((states - 1) * alphabet.size(), init.getIncomingTransitions().size()); automaton.removeAllTransitions(s2, alphabet.getSymbol(0)); - Assert.assertEquals((states - 1) * alphabet.size() - 1, - automaton.getInitialState().getIncomingTransitions().size()); + Assert.assertEquals((states - 1) * alphabet.size() - 1, init.getIncomingTransitions().size()); automaton.addTransition(s2, alphabet.getSymbol(0), s1, 0); for (int i = 1; i < alphabet.size(); i++) { ADTTransition transition = automaton.getTransition(s2, alphabet.getSymbol(i)); + Assert.assertNotNull(transition); transition.setTarget(s1); } diff --git a/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/learner/ADTVisualizationTest.java b/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/learner/ADTVisualizationTest.java index 26e73a1fff..9e3e817524 100644 --- a/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/learner/ADTVisualizationTest.java +++ b/algorithms/active/adt/src/test/java/de/learnlib/algorithms/adt/learner/ADTVisualizationTest.java @@ -24,6 +24,7 @@ import de.learnlib.testsupport.AbstractVisualizationTest; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.words.Alphabet; +import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.testng.Assert; import org.testng.annotations.Test; @@ -33,7 +34,9 @@ public class ADTVisualizationTest extends AbstractVisualizationTest> { @Override - protected ADTLearner getLearnerBuilder(Alphabet alphabet, SUL sul) { + protected ADTLearner getLearnerBuilder(@UnderInitialization ADTVisualizationTest this, + Alphabet alphabet, + SUL sul) { return new ADTLearnerBuilder().withAlphabet(alphabet) .withOracle(new SULSymbolQueryOracle<>(sul)) .create(); diff --git a/algorithms/active/dhc/pom.xml b/algorithms/active/dhc/pom.xml index 93cc5afeba..786198e9b3 100644 --- a/algorithms/active/dhc/pom.xml +++ b/algorithms/active/dhc/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -64,6 +64,11 @@ limitations under the License. automata-commons-util + + org.checkerframework + checker-qual + + org.slf4j slf4j-api diff --git a/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHC.java b/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHC.java index 2ce1ecff64..8d08a0f2c5 100644 --- a/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHC.java +++ b/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHC.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import com.github.misberner.buildergen.annotations.GenerateBuilder; import com.google.common.collect.Interner; @@ -46,6 +47,8 @@ import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +64,7 @@ public class MealyDHC implements MealyLearner, private static final Logger LOG = LoggerFactory.getLogger(MealyDHC.class); private final MembershipOracle> oracle; private final Alphabet alphabet; - private LinkedHashSet> splitters = new LinkedHashSet<>(); + private Set> splitters = new LinkedHashSet<>(); private CompactMealy hypothesis; private MutableMapping> accessSequences; private final GlobalSuffixFinder> suffixFinder; @@ -75,7 +78,7 @@ public class MealyDHC implements MealyLearner, * the learning membership oracle */ public MealyDHC(Alphabet alphabet, MembershipOracle> oracle) { - this(alphabet, oracle, GlobalSuffixFinders.RIVEST_SCHAPIRE, null); + this(alphabet, oracle, GlobalSuffixFinders.RIVEST_SCHAPIRE, Collections.emptyList()); } /** @@ -163,7 +166,8 @@ public void startLearning() { while (!queue.isEmpty()) { // get element to be explored from queue - QueueElement elem = queue.poll(); + @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399 + @NonNull QueueElement elem = queue.poll(); // determine access sequence for state Word access = assembleAccessSequence(elem); @@ -320,13 +324,16 @@ public static Collection> initialSplitters() { static final class QueueElement implements Serializable { - private final Integer parentState; - private final QueueElement parentElement; - private final I transIn; - private final O transOut; + private final @Nullable Integer parentState; + private final @Nullable QueueElement parentElement; + private final @Nullable I transIn; + private final @Nullable O transOut; private final int depth; - QueueElement(Integer parentState, QueueElement parentElement, I transIn, O transOut) { + QueueElement(@Nullable Integer parentState, + @Nullable QueueElement parentElement, + @Nullable I transIn, + @Nullable O transOut) { this.parentState = parentState; this.parentElement = parentElement; this.transIn = transIn; diff --git a/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHCState.java b/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHCState.java index 4c3522e9dc..3891815f00 100644 --- a/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHCState.java +++ b/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHCState.java @@ -16,8 +16,8 @@ package de.learnlib.algorithms.dhc.mealy; import java.io.Serializable; -import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import com.google.common.collect.Maps; import net.automatalib.automata.transducers.impl.compact.CompactMealy; @@ -36,11 +36,11 @@ */ public class MealyDHCState implements Serializable { - private final LinkedHashSet> splitters; + private final Set> splitters; private final CompactMealy hypothesis; private final Map> accessSequences; - MealyDHCState(final LinkedHashSet> splitters, + MealyDHCState(final Set> splitters, final CompactMealy hypothesis, final MutableMapping> accessSequences) { this.splitters = splitters; @@ -55,7 +55,7 @@ public class MealyDHCState implements Serializable { } } - LinkedHashSet> getSplitters() { + Set> getSplitters() { return splitters; } diff --git a/algorithms/active/discrimination-tree-vpda/pom.xml b/algorithms/active/discrimination-tree-vpda/pom.xml index 2c1bde9693..e7672b6827 100644 --- a/algorithms/active/discrimination-tree-vpda/pom.xml +++ b/algorithms/active/discrimination-tree-vpda/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -58,6 +58,11 @@ limitations under the License. guava + + org.checkerframework + checker-qual + + net.automatalib automata-api diff --git a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/vpda/OneSEVPAHypothesis.java b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/vpda/OneSEVPAHypothesis.java index 17375c62e5..18ddc5fe61 100644 --- a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/vpda/OneSEVPAHypothesis.java +++ b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/vpda/OneSEVPAHypothesis.java @@ -19,6 +19,7 @@ import java.util.List; import net.automatalib.automata.vpda.AbstractOneSEVPA; +import net.automatalib.automata.vpda.StackContents; import net.automatalib.automata.vpda.State; import net.automatalib.words.VPDAlphabet; @@ -43,8 +44,10 @@ public AbstractHypTrans getInternalTransition(State> state, I sym) case INTERNAL: return state.getLocation().getInternalTransition(alphabet.getInternalSymbolIndex(sym)); case RETURN: + StackContents stackContents = state.getStackContents(); + assert stackContents != null; return state.getLocation() - .getReturnTransition(alphabet.getReturnSymbolIndex(sym), state.getStackContents().peek()); + .getReturnTransition(alphabet.getReturnSymbolIndex(sym), stackContents.peek()); default: return null; } diff --git a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/AbstractVPDALearner.java b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/AbstractVPDALearner.java index 1de979d679..144839d51f 100644 --- a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/AbstractVPDALearner.java +++ b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/AbstractVPDALearner.java @@ -100,8 +100,9 @@ protected static void link(DTNode leaf, HypLoc loc) { } protected void initializeLocation(HypLoc loc) { - assert loc.getLeaf() != null; - loc.setAccepting(dtree.getRoot().subtreeLabel(loc.getLeaf())); + final Boolean subtreeLabel = dtree.getRoot().subtreeLabel(loc.getLeaf()); + assert subtreeLabel != null; + loc.setAccepting(subtreeLabel); for (int i = 0; i < alphabet.getNumInternals(); i++) { I intSym = alphabet.getInternalSymbol(i); diff --git a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/DTLearnerVPDA.java b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/DTLearnerVPDA.java index 9b2d3bfb27..3294f9cc44 100644 --- a/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/DTLearnerVPDA.java +++ b/algorithms/active/discrimination-tree-vpda/src/main/java/de/learnlib/algorithms/discriminationtree/vpda/DTLearnerVPDA.java @@ -33,6 +33,7 @@ import net.automatalib.words.VPDAlphabet; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; /** * @param @@ -58,11 +59,11 @@ protected Word transformAccessSequence(State> state) { return transformAccessSequence(state.getStackContents(), state.getLocation()); } - protected Word transformAccessSequence(StackContents contents) { + protected Word transformAccessSequence(@Nullable StackContents contents) { return transformAccessSequence(contents, hypothesis.getInitialLocation()); } - protected Word transformAccessSequence(StackContents contents, HypLoc loc) { + protected Word transformAccessSequence(@Nullable StackContents contents, HypLoc loc) { List stackElems = new ArrayList<>(); if (contents != null) { StackContents iter = contents; @@ -101,11 +102,14 @@ protected boolean refineHypothesisSingle(DefaultQuery ceQuery) { Word suffix = ceWord.subWord(breakpoint + 1); State> state = hypothesis.getState(prefix); + assert state != null; State> succState = hypothesis.getSuccessor(state, act); + assert succState != null; ContextPair context = new ContextPair<>(transformAccessSequence(succState.getStackContents()), suffix); AbstractHypTrans trans = hypothesis.getInternalTransition(state, act); + assert trans != null; HypLoc newLoc = makeTree(trans); DTNode oldDtNode = succState.getLocation().getLeaf(); diff --git a/algorithms/active/discrimination-tree/pom.xml b/algorithms/active/discrimination-tree/pom.xml index 34b49e1869..92fc6b79b9 100644 --- a/algorithms/active/discrimination-tree/pom.xml +++ b/algorithms/active/discrimination-tree/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/AbstractDTLearner.java b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/AbstractDTLearner.java index 4b79b2e427..762968971b 100644 --- a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/AbstractDTLearner.java +++ b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/AbstractDTLearner.java @@ -105,10 +105,12 @@ protected boolean refineHypothesisSingle(DefaultQuery ceQuery) { Word input = ceQuery.getInput(); Word oldStateAs = input.prefix(suffixIdx); HState oldState = hypothesis.getState(oldStateAs); + assert oldState != null; AbstractWordBasedDTNode> oldDt = oldState.getDTLeaf(); Word newPredAs = input.prefix(suffixIdx - 1); HState newPred = hypothesis.getState(newPredAs); + assert newPred != null; I transSym = input.getSymbol(suffixIdx - 1); int transIdx = alphabet.getSymbolIndex(transSym); HTransition trans = newPred.getTransition(transIdx); diff --git a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/dfa/HypothesisWrapperDFA.java b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/dfa/HypothesisWrapperDFA.java index 86ce052ad7..258856273e 100644 --- a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/dfa/HypothesisWrapperDFA.java +++ b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/dfa/HypothesisWrapperDFA.java @@ -20,6 +20,7 @@ import de.learnlib.algorithms.discriminationtree.hypothesis.DTLearnerHypothesis; import de.learnlib.algorithms.discriminationtree.hypothesis.HState; import net.automatalib.automata.fsa.DFA; +import org.checkerframework.checker.nullness.qual.Nullable; final class HypothesisWrapperDFA implements DFA, I> { @@ -40,7 +41,7 @@ public HState getInitialState() { } @Override - public HState getTransition(HState state, I input) { + public @Nullable HState getTransition(HState state, I input) { return dtHypothesis.getSuccessor(state, input); } diff --git a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/DTLearnerHypothesis.java b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/DTLearnerHypothesis.java index d161cc04b3..36d0f13ed0 100644 --- a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/DTLearnerHypothesis.java +++ b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/DTLearnerHypothesis.java @@ -108,12 +108,17 @@ public int getStateId(HState state) { @Override public HState getState(int id) { - return (id < 0 || id >= nodes.size()) ? null : nodes.get(id); + if (id < 0 || id >= nodes.size()) { + throw new IndexOutOfBoundsException("No valid id"); + } + + return nodes.get(id); } @Override public Word transformAccessSequence(Word word) { HState state = getState(word); + assert state != null; return state.getAccessSequence(); } diff --git a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HState.java b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HState.java index 905d300cc4..5503fe06ac 100644 --- a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HState.java +++ b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HState.java @@ -26,10 +26,11 @@ import net.automatalib.commons.smartcollections.ResizingArrayStorage; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; public class HState implements Serializable { - private final HTransition treeIncoming; + private final @Nullable HTransition treeIncoming; private final int id; private final int depth; private final ResizingArrayStorage> transitions; @@ -41,7 +42,7 @@ public HState(int alphabetSize) { this(alphabetSize, 0, null); } - public HState(int initialAlphabetSize, int id, HTransition treeIncoming) { + public HState(int initialAlphabetSize, int id, @Nullable HTransition treeIncoming) { this.id = id; this.treeIncoming = treeIncoming; this.depth = (treeIncoming == null) ? 0 : treeIncoming.getSource().depth + 1; @@ -56,7 +57,7 @@ public void setDTLeaf(AbstractWordBasedDTNode> dtLeaf this.dtLeaf = dtLeaf; } - public HTransition getTreeIncoming() { + public @Nullable HTransition getTreeIncoming() { return treeIncoming; } diff --git a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HTransition.java b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HTransition.java index fa84805f58..dff5402c71 100644 --- a/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HTransition.java +++ b/algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/HTransition.java @@ -20,6 +20,8 @@ import de.learnlib.datastructure.discriminationtree.model.AbstractWordBasedDTNode; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; public class HTransition implements Serializable { @@ -29,10 +31,10 @@ public class HTransition implements Serializable { private TP property; // TREE EDGE FIELDS - private HState treeTgt; + private @Nullable HState treeTgt; // NON-TREE EDGE FIELDS - private AbstractWordBasedDTNode> dt; + private @Nullable AbstractWordBasedDTNode> dt; public HTransition(HState source, I symbol, @@ -64,12 +66,18 @@ public HState getTreeTarget() { return treeTgt; } + @EnsuresNonNullIf(expression = "treeTgt", result = true) public boolean isTree() { return (treeTgt != null); } + @EnsuresNonNullIf(expression = "dt", result = true) + public boolean isNonTree() { + return (dt != null); + } + public AbstractWordBasedDTNode> getDT() { - assert !isTree(); + assert isNonTree(); return dt; } @@ -96,15 +104,18 @@ public Word getAccessSequence() { } public HState nonTreeTarget() { - assert !isTree(); + assert isNonTree(); return dt.getData(); } public HState currentTarget() { if (treeTgt != null) { return treeTgt; + } else if (dt != null) { + return dt.getData(); + } else { + throw new IllegalStateException("Neither tree target not DT node provided"); } - return dt.getData(); } } diff --git a/algorithms/active/discrimination-tree/src/test/java/de/learnlib/algorithms/discriminationtree/DTVisualizationTest.java b/algorithms/active/discrimination-tree/src/test/java/de/learnlib/algorithms/discriminationtree/DTVisualizationTest.java index 1fb994eedb..ddea30fd0d 100644 --- a/algorithms/active/discrimination-tree/src/test/java/de/learnlib/algorithms/discriminationtree/DTVisualizationTest.java +++ b/algorithms/active/discrimination-tree/src/test/java/de/learnlib/algorithms/discriminationtree/DTVisualizationTest.java @@ -26,6 +26,7 @@ import de.learnlib.testsupport.AbstractVisualizationTest; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.words.Alphabet; +import org.checkerframework.checker.initialization.qual.UnderInitialization; import org.testng.Assert; import org.testng.annotations.Test; @@ -35,7 +36,9 @@ public class DTVisualizationTest extends AbstractVisualizationTest> { @Override - protected DTLearnerMealy getLearnerBuilder(Alphabet alphabet, SUL sul) { + protected DTLearnerMealy getLearnerBuilder(@UnderInitialization DTVisualizationTest this, + Alphabet alphabet, + SUL sul) { return new DTLearnerMealyBuilder().withAlphabet(alphabet) .withOracle(new SULOracle<>(sul)) .create(); diff --git a/algorithms/active/kearns-vazirani/pom.xml b/algorithms/active/kearns-vazirani/pom.xml index f013a944c3..13f0ed8383 100644 --- a/algorithms/active/kearns-vazirani/pom.xml +++ b/algorithms/active/kearns-vazirani/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -63,6 +63,11 @@ limitations under the License. automata-core + + org.checkerframework + checker-qual + + com.github.misberner.buildergen diff --git a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/dfa/KearnsVaziraniDFA.java b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/dfa/KearnsVaziraniDFA.java index 4bb5e17f19..bf66bf9bbd 100644 --- a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/dfa/KearnsVaziraniDFA.java +++ b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/dfa/KearnsVaziraniDFA.java @@ -400,7 +400,9 @@ protected Boolean computeEffect(int index) { AbstractWordBasedDTNode> node = info.dtNode; Deque expect = new ArrayDeque<>(); while (!node.isRoot()) { - expect.push(node.getParentOutcome()); + Boolean parentOutcome = node.getParentOutcome(); + assert parentOutcome != null; + expect.push(parentOutcome); node = node.getParent(); } diff --git a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java index 57814010ee..ac3f239b12 100644 --- a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java +++ b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java @@ -39,9 +39,11 @@ import net.automatalib.SupportsGrowingAlphabet; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactMealy; +import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An adaption of the Kearns/Vazirani algorithm for Mealy machines. @@ -132,7 +134,8 @@ private boolean refineHypothesisSingle(Word input, Word output) { Word prefix = effInput.prefix(idx); StateInfo> srcStateInfo = acex.getStateInfo(idx); I sym = effInput.getSymbol(idx); - LCAInfo, AbstractWordBasedDTNode, StateInfo>>> lca = acex.getLCA(idx + 1); + LCAInfo, @Nullable AbstractWordBasedDTNode, StateInfo>>> lca = + acex.getLCA(idx + 1); assert lca != null; splitState(srcStateInfo, prefix, sym, lca); @@ -143,7 +146,7 @@ private boolean refineHypothesisSingle(Word input, Word output) { private void splitState(StateInfo> stateInfo, Word newPrefix, I sym, - LCAInfo, AbstractWordBasedDTNode, StateInfo>>> separatorInfo) { + LCAInfo, @Nullable AbstractWordBasedDTNode, StateInfo>>> separatorInfo) { int state = stateInfo.id; // TLongList oldIncoming = stateInfo.fetchIncoming(); @@ -162,7 +165,9 @@ private void splitState(StateInfo> stateInfo, newOut = separatorInfo.subtree2Label; } else { newDiscriminator = newDiscriminator(sym, separator.getDiscriminator()); - O transOut = hypothesis.getOutput(state, sym); + CompactMealyTransition transition = hypothesis.getTransition(state, sym); + assert transition != null; + O transOut = hypothesis.getTransitionOutput(transition); oldOut = newOutcome(transOut, separatorInfo.subtree1Label); newOut = newOutcome(transOut, separatorInfo.subtree2Label); } @@ -209,10 +214,9 @@ private void updateTransitions(List transList, int sourceState = (int) (encodedTrans >> Integer.SIZE); int transIdx = (int) (encodedTrans); - setTransition(sourceState, - transIdx, - succs.get(i), - hypothesis.getTransition(sourceState, transIdx).getOutput()); + CompactMealyTransition trans = hypothesis.getTransition(sourceState, transIdx); + assert trans != null; + setTransition(sourceState, transIdx, succs.get(i), trans.getOutput()); } } @@ -378,7 +382,7 @@ protected class KVAbstractCounterexample extends AbstractBaseCounterexample ceWord; private final MembershipOracle> oracle; private final StateInfo>[] states; - private final LCAInfo, AbstractWordBasedDTNode, StateInfo>>>[] lcas; + private final LCAInfo, @Nullable AbstractWordBasedDTNode, StateInfo>>>[] lcas; @SuppressWarnings("unchecked") public KVAbstractCounterexample(Word ceWord, Word output, MembershipOracle> oracle) { @@ -408,7 +412,7 @@ public StateInfo> getStateInfo(int idx) { return states[idx]; } - public LCAInfo, AbstractWordBasedDTNode, StateInfo>>> getLCA(int idx) { + public LCAInfo, @Nullable AbstractWordBasedDTNode, StateInfo>>> getLCA(int idx) { return lcas[idx]; } @@ -422,7 +426,9 @@ protected Boolean computeEffect(int index) { AbstractWordBasedDTNode, StateInfo>> node = info.dtNode; Deque> expect = new ArrayDeque<>(); while (!node.isRoot()) { - expect.push(node.getParentOutcome()); + Word parentOutcome = node.getParentOutcome(); + assert parentOutcome != null; + expect.push(parentOutcome); node = node.getParent(); } diff --git a/algorithms/active/lstar/pom.xml b/algorithms/active/lstar/pom.xml index e6139820d5..eb3497db2a 100644 --- a/algorithms/active/lstar/pom.xml +++ b/algorithms/active/lstar/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -70,6 +70,11 @@ limitations under the License. automata-util + + org.checkerframework + checker-qual + + com.github.misberner.buildergen diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractAutomatonLStar.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractAutomatonLStar.java index 227a9c5889..5a8dd2e706 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractAutomatonLStar.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractAutomatonLStar.java @@ -86,6 +86,7 @@ public final void startLearning() { * #stateProperty(ObservationTable, Row)} and {@link #transitionProperty(ObservationTable, Row, int)} methods are * used to derive the respective properties. */ + @SuppressWarnings("argument.type.incompatible") // all added nulls to stateInfos will be correctly set to non-null values protected void updateInternalHypothesis() { if (!table.isInitialized()) { throw new IllegalStateException("Cannot update internal hypothesis: not initialized"); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractExtensibleAutomatonLStar.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractExtensibleAutomatonLStar.java index 329d5bf0c5..43ac80b07c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractExtensibleAutomatonLStar.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractExtensibleAutomatonLStar.java @@ -59,12 +59,12 @@ protected void refineHypothesisInternal(DefaultQuery ceQuery) { } @Override - protected List> initialPrefixes() { + protected final List> initialPrefixes() { return initialPrefixes; } @Override - protected List> initialSuffixes() { + protected final List> initialSuffixes() { return initialSuffixes; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/ce/ObservationTableCEXHandlers.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/ce/ObservationTableCEXHandlers.java index c4985a5436..a3f48388e6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/ce/ObservationTableCEXHandlers.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/ce/ObservationTableCEXHandlers.java @@ -29,11 +29,12 @@ import de.learnlib.datastructure.observationtable.Row; import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; public final class ObservationTableCEXHandlers { - public static final ObservationTableCEXHandler CLASSIC_LSTAR = - new ObservationTableCEXHandler() { + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> CLASSIC_LSTAR = + new ObservationTableCEXHandler<@Nullable Object, @Nullable Object>() { @Override public List>> handleCounterexample(DefaultQuery ceQuery, @@ -44,19 +45,19 @@ public List>> handleCounterexample(DefaultQuery ce } @Override - public String toString() { - return "ClassicLStar"; + public boolean needsConsistencyCheck() { + return true; } @Override - public boolean needsConsistencyCheck() { - return true; + public String toString() { + return "ClassicLStar"; } }; - public static final ObservationTableCEXHandler SUFFIX1BY1 = - new ObservationTableCEXHandler() { + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> SUFFIX1BY1 = + new ObservationTableCEXHandler<@Nullable Object, @Nullable Object>() { @Override public List>> handleCounterexample(DefaultQuery ceQuery, @@ -77,28 +78,28 @@ public String toString() { } }; - public static final ObservationTableCEXHandler MALER_PNUELI = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> MALER_PNUELI = fromGlobalSuffixFinder(GlobalSuffixFinders.MALER_PNUELI); - public static final ObservationTableCEXHandler SHAHBAZ = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> SHAHBAZ = fromGlobalSuffixFinder(GlobalSuffixFinders.SHAHBAZ); - public static final ObservationTableCEXHandler FIND_LINEAR = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> FIND_LINEAR = fromLocalSuffixFinder(LocalSuffixFinders.FIND_LINEAR, false); - public static final ObservationTableCEXHandler FIND_LINEAR_ALLSUFFIXES = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> FIND_LINEAR_ALLSUFFIXES = fromLocalSuffixFinder(LocalSuffixFinders.FIND_LINEAR, true); - public static final ObservationTableCEXHandler FIND_LINEAR_REVERSE = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> FIND_LINEAR_REVERSE = fromLocalSuffixFinder(LocalSuffixFinders.FIND_LINEAR_REVERSE, false); - public static final ObservationTableCEXHandler FIND_LINEAR_REVERSE_ALLSUFFIXES = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> FIND_LINEAR_REVERSE_ALLSUFFIXES = fromLocalSuffixFinder(LocalSuffixFinders.FIND_LINEAR_REVERSE, true); - public static final ObservationTableCEXHandler RIVEST_SCHAPIRE = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> RIVEST_SCHAPIRE = fromLocalSuffixFinder(LocalSuffixFinders.RIVEST_SCHAPIRE, false); - public static final ObservationTableCEXHandler RIVEST_SCHAPIRE_ALLSUFFIXES = + public static final ObservationTableCEXHandler<@Nullable Object, @Nullable Object> RIVEST_SCHAPIRE_ALLSUFFIXES = fromLocalSuffixFinder(LocalSuffixFinders.RIVEST_SCHAPIRE, true); private ObservationTableCEXHandlers() { @@ -209,7 +210,7 @@ public static List>> handleSuffix1by1(DefaultQuery ceQu } @SuppressWarnings("unchecked") - public static ObservationTableCEXHandler[] values() { + public static ObservationTableCEXHandler<@Nullable Object, @Nullable Object>[] values() { return new ObservationTableCEXHandler[] {CLASSIC_LSTAR, SUFFIX1BY1, MALER_PNUELI, diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/CloseRandomStrategy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/CloseRandomStrategy.java index 2a714c2459..036b7656be 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/CloseRandomStrategy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/CloseRandomStrategy.java @@ -22,8 +22,9 @@ import de.learnlib.api.oracle.MembershipOracle; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; +import org.checkerframework.checker.nullness.qual.Nullable; -public class CloseRandomStrategy implements ClosingStrategy { +public class CloseRandomStrategy implements ClosingStrategy<@Nullable Object, @Nullable Object> { private final Random random; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/ClosingStrategies.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/ClosingStrategies.java index 701d7e3a60..9230eddd20 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/ClosingStrategies.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/closing/ClosingStrategies.java @@ -23,6 +23,7 @@ import de.learnlib.datastructure.observationtable.Row; import net.automatalib.commons.util.comparison.CmpUtil; import net.automatalib.words.Alphabet; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Collection of predefined observation table closing strategies. @@ -35,101 +36,106 @@ public final class ClosingStrategies { /** * Closing strategy that randomly selects one representative row to close from each equivalence class. */ - public static final ClosingStrategy CLOSE_RANDOM = new CloseRandomStrategy(); + public static final ClosingStrategy<@Nullable Object, @Nullable Object> CLOSE_RANDOM = new CloseRandomStrategy(); /** * Closing strategy that selects the first row from each equivalence class as representative. */ - public static final ClosingStrategy CLOSE_FIRST = new ClosingStrategy() { - - @Override - public List> selectClosingRows(List>> unclosedClasses, - ObservationTable table, - MembershipOracle oracle) { - List> result = new ArrayList<>(unclosedClasses.size()); - for (List> clazz : unclosedClasses) { - result.add(clazz.get(0)); - } - return result; - } - - @Override - public String toString() { - return "CloseFirst"; - } - }; + public static final ClosingStrategy<@Nullable Object, @Nullable Object> CLOSE_FIRST = + new ClosingStrategy<@Nullable Object, @Nullable Object>() { + + @Override + public List> selectClosingRows(List>> unclosedClasses, + ObservationTable table, + MembershipOracle oracle) { + List> result = new ArrayList<>(unclosedClasses.size()); + for (List> clazz : unclosedClasses) { + result.add(clazz.get(0)); + } + return result; + } + + @Override + public String toString() { + return "CloseFirst"; + } + }; /** * Closing strategy that selects the shortest row of each equivalence class (more precisely: a row which's prefix * has minimal length in the respective class) as representative. */ - public static final ClosingStrategy CLOSE_SHORTEST = new ClosingStrategy() { - - @Override - public List> selectClosingRows(List>> unclosedClasses, - ObservationTable table, - MembershipOracle oracle) { - - List> result = new ArrayList<>(); - for (List> clazz : unclosedClasses) { - Row shortest = null; - int shortestLen = Integer.MAX_VALUE; - for (Row row : clazz) { - int prefixLen = row.getLabel().length(); - if (shortest == null || prefixLen < shortestLen) { - shortest = row; - shortestLen = prefixLen; + public static final ClosingStrategy<@Nullable Object, @Nullable Object> CLOSE_SHORTEST = + new ClosingStrategy<@Nullable Object, @Nullable Object>() { + + @Override + public List> selectClosingRows(List>> unclosedClasses, + ObservationTable table, + MembershipOracle oracle) { + + List> result = new ArrayList<>(); + for (List> clazz : unclosedClasses) { + Row shortest = null; + int shortestLen = Integer.MAX_VALUE; + for (Row row : clazz) { + int prefixLen = row.getLabel().length(); + if (shortest == null || prefixLen < shortestLen) { + shortest = row; + shortestLen = prefixLen; + } + } + assert shortest != null; + result.add(shortest); } + return result; } - result.add(shortest); - } - return result; - } - @Override - public String toString() { - return "CloseShortest"; - } - }; + @Override + public String toString() { + return "CloseShortest"; + } + }; /** * Closing strategy that selects the lexicographically minimal row (wrt. its prefix) of each equivalence class as * representative. */ - public static final ClosingStrategy CLOSE_LEX_MIN = new ClosingStrategy() { - - @Override - public List> selectClosingRows(List>> unclosedClasses, - ObservationTable table, - MembershipOracle oracle) { - List> result = new ArrayList<>(unclosedClasses.size()); - Alphabet alphabet = table.getInputAlphabet(); - for (List> clazz : unclosedClasses) { - Row lexMin = null; - for (Row row : clazz) { - if (lexMin == null) { - lexMin = row; - } else if (CmpUtil.lexCompare(row.getLabel(), lexMin.getLabel(), alphabet) < 0) { - lexMin = row; + public static final ClosingStrategy<@Nullable Object, @Nullable Object> CLOSE_LEX_MIN = + new ClosingStrategy<@Nullable Object, @Nullable Object>() { + + @Override + public List> selectClosingRows(List>> unclosedClasses, + ObservationTable table, + MembershipOracle oracle) { + List> result = new ArrayList<>(unclosedClasses.size()); + Alphabet alphabet = table.getInputAlphabet(); + for (List> clazz : unclosedClasses) { + Row lexMin = null; + for (Row row : clazz) { + if (lexMin == null) { + lexMin = row; + } else if (CmpUtil.lexCompare(row.getLabel(), lexMin.getLabel(), alphabet) < 0) { + lexMin = row; + } + } + assert lexMin != null; + result.add(lexMin); } + return result; } - result.add(lexMin); - } - return result; - } - @Override - public String toString() { - return "CloseLexMin"; - } - }; + @Override + public String toString() { + return "CloseLexMin"; + } + }; private ClosingStrategies() { // prevent instantiation } @SuppressWarnings("unchecked") - public static ClosingStrategy[] values() { + public static ClosingStrategy<@Nullable Object, @Nullable Object>[] values() { return new ClosingStrategy[] {CLOSE_RANDOM, CLOSE_FIRST, CLOSE_SHORTEST, CLOSE_LEX_MIN}; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/dfa/ExtensibleLStarDFA.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/dfa/ExtensibleLStarDFA.java index 6138afe2fc..71c180fe0e 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/dfa/ExtensibleLStarDFA.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/dfa/ExtensibleLStarDFA.java @@ -77,11 +77,6 @@ public ExtensibleLStarDFA(Alphabet alphabet, closingStrategy); } - @Override - protected List> initialSuffixes() { - return Collections.singletonList(Word.epsilon()); - } - @Override protected DFA exposeInternalHypothesis() { return internalHyp; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java index 212f5c0455..449a774f7a 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ClassicLStarMealy.java @@ -15,7 +15,6 @@ */ package de.learnlib.algorithms.lstar.mealy; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -29,11 +28,11 @@ import de.learnlib.util.mealy.MealyUtil; import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.automata.transducers.MealyMachine; -import net.automatalib.automata.transducers.MutableMealyMachine; import net.automatalib.automata.transducers.impl.compact.CompactMealy; import net.automatalib.automata.transducers.impl.compact.CompactMealyTransition; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An implementation of the L*Mealy algorithm for inferring Mealy machines, as described by Oliver Niese in his Ph.D. @@ -47,7 +46,7 @@ * @author Malte Isberner */ public class ClassicLStarMealy - extends AbstractExtensibleAutomatonLStar, I, O, Integer, CompactMealyTransition, Void, O, CompactMealy> { + extends AbstractExtensibleAutomatonLStar, I, @Nullable O, Integer, CompactMealyTransition, Void, O, CompactMealy> { /** * Constructor. @@ -58,7 +57,7 @@ public class ClassicLStarMealy * the (Mealy) oracle */ public ClassicLStarMealy(Alphabet alphabet, - MembershipOracle oracle, + MembershipOracle oracle, ObservationTableCEXHandler cexHandler, ClosingStrategy closingStrategy) { this(alphabet, @@ -71,7 +70,7 @@ public ClassicLStarMealy(Alphabet alphabet, @GenerateBuilder(defaults = AbstractExtensibleAutomatonLStar.BuilderDefaults.class) public ClassicLStarMealy(Alphabet alphabet, - MembershipOracle oracle, + MembershipOracle oracle, List> initialPrefixes, List> initialSuffixes, ObservationTableCEXHandler cexHandler, @@ -85,65 +84,52 @@ public ClassicLStarMealy(Alphabet alphabet, closingStrategy); } - public static , I, O> ClassicLStarMealy createForSymbolOracle( - Alphabet alphabet, - MembershipOracle oracle, - ObservationTableCEXHandler cexHandler, - ClosingStrategy closingStrategy) { + public static ClassicLStarMealy createForSymbolOracle(Alphabet alphabet, + MembershipOracle oracle, + ObservationTableCEXHandler cexHandler, + ClosingStrategy closingStrategy) { return new ClassicLStarMealy<>(alphabet, oracle, cexHandler, closingStrategy); } - public static , I, O> ClassicLStarMealy createForWordOracle(Alphabet alphabet, - MembershipOracle> oracle, - ObservationTableCEXHandler cexHandler, - ClosingStrategy closingStrategy) { + public static ClassicLStarMealy createForWordOracle(Alphabet alphabet, + MembershipOracle> oracle, + ObservationTableCEXHandler cexHandler, + ClosingStrategy closingStrategy) { return new ClassicLStarMealy<>(alphabet, MealyUtil.wrapWordOracle(oracle), cexHandler, closingStrategy); } - @Override - protected List> initialSuffixes() { - List> suffixes = new ArrayList<>(alphabet.size()); - for (int i = 0; i < alphabet.size(); i++) { - I sym = alphabet.getSymbol(i); - suffixes.add(Word.fromLetter(sym)); - } - return suffixes; - } - @Override protected MealyMachine exposeInternalHypothesis() { return internalHyp; } @Override - protected Void stateProperty(ObservationTable table, Row stateRow) { + protected Void stateProperty(ObservationTable table, Row stateRow) { return null; } @Override - protected O transitionProperty(ObservationTable table, Row stateRow, int inputIdx) { + protected O transitionProperty(ObservationTable table, Row stateRow, int inputIdx) { return table.cellContents(stateRow, inputIdx); } @Override - protected SuffixOutput hypothesisOutput() { - return new SuffixOutput() { + protected SuffixOutput hypothesisOutput() { + return new SuffixOutput() { @Override - public O computeOutput(Iterable input) { + public @Nullable O computeOutput(Iterable input) { return computeSuffixOutput(Collections.emptyList(), input); } @Override - public O computeSuffixOutput(Iterable prefix, Iterable suffix) { + public @Nullable O computeSuffixOutput(Iterable prefix, Iterable suffix) { Word wordOut = internalHyp.computeSuffixOutput(prefix, suffix); if (wordOut.isEmpty()) { return null; } return wordOut.lastSymbol(); } - }; } - } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java index b0862eb20f..121b6627b8 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/mealy/ExtensibleLStarMealy.java @@ -65,11 +65,6 @@ public ExtensibleLStarMealy(Alphabet alphabet, closingStrategy); } - @Override - protected List> initialSuffixes() { - return initialSuffixes; - } - @Override public CompactMealy getHypothesisModel() { return internalHyp; diff --git a/algorithms/active/nlstar/pom.xml b/algorithms/active/nlstar/pom.xml index 71f0f2f9a1..5a5387ad27 100644 --- a/algorithms/active/nlstar/pom.xml +++ b/algorithms/active/nlstar/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 learnlib-nlstar @@ -70,6 +70,11 @@ limitations under the License. de.learnlib.testsupport learnlib-learner-it-support + + de.learnlib + learnlib-equivalence-oracles + test + de.learnlib learnlib-membership-oracles diff --git a/algorithms/active/nlstar/src/main/java/de/learnlib/algorithms/nlstar/Row.java b/algorithms/active/nlstar/src/main/java/de/learnlib/algorithms/nlstar/Row.java index 6655f16d76..bca5a340b1 100644 --- a/algorithms/active/nlstar/src/main/java/de/learnlib/algorithms/nlstar/Row.java +++ b/algorithms/active/nlstar/src/main/java/de/learnlib/algorithms/nlstar/Row.java @@ -129,7 +129,9 @@ public List> getCoveredRows() { } boolean checkPrime() { - if (coveredRows.isEmpty()) { + if (contents.isEmpty()) { + prime = false; + } else if (coveredRows.isEmpty()) { prime = true; } else { BitSet aggContents = new BitSet(); diff --git a/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarTest.java b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarTest.java new file mode 100644 index 0000000000..144f42e407 --- /dev/null +++ b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarTest.java @@ -0,0 +1,73 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.algorithms.nlstar; + +import de.learnlib.oracle.equivalence.SampleSetEQOracle; +import de.learnlib.oracle.membership.SimulatorOracle; +import de.learnlib.util.Experiment; +import net.automatalib.automata.fsa.NFA; +import net.automatalib.automata.fsa.impl.compact.CompactNFA; +import net.automatalib.util.automata.Automata; +import net.automatalib.util.automata.builders.AutomatonBuilders; +import net.automatalib.util.automata.fsa.NFAs; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class NLStarTest { + + /** + * Test case for the bug described in issue #70. + */ + @Test + public void testIssue70() { + final Alphabet alphabet = Alphabets.characters('a', 'b'); + + // @formatter:off + final CompactNFA nfa = AutomatonBuilders.newNFA(alphabet) + .withInitial("q0") + .from("q0") + .on('a').to("q1") + .on('b').to("q2") + .from("q1").on('b').to("q1") + .from("q2").on('a').to("q2") + .withAccepting("q1", "q2") + .create(); + // @formatter:on + + final SimulatorOracle mqOracle = new SimulatorOracle<>(nfa); + + final SampleSetEQOracle eqOracle = new SampleSetEQOracle<>(false); + eqOracle.addAll(mqOracle, + Word.fromCharSequence("a"), + Word.fromCharSequence("ab"), + Word.fromCharSequence("aa"), + Word.fromCharSequence("bab")); + + final NLStarLearner learner = new NLStarLearner<>(alphabet, mqOracle); + + final Experiment> experiment = new Experiment<>(learner, eqOracle, alphabet); + experiment.run(); + final NFA hyp = experiment.getFinalHypothesis(); + + Assert.assertEquals(nfa.size(), hyp.size()); + Assert.assertTrue(Automata.testEquivalence(NFAs.determinize(nfa, false, false), + NFAs.determinize(hyp, alphabet, false, false), + alphabet)); + } +} diff --git a/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarIT.java b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/it/NLStarIT.java similarity index 94% rename from algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarIT.java rename to algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/it/NLStarIT.java index db43b179d9..baf989be80 100644 --- a/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/NLStarIT.java +++ b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithms/nlstar/it/NLStarIT.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.learnlib.algorithms.nlstar; +package de.learnlib.algorithms.nlstar.it; +import de.learnlib.algorithms.nlstar.NLStarLearner; import de.learnlib.api.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.testsupport.it.learner.AbstractDFALearnerIT; import de.learnlib.testsupport.it.learner.LearnerVariantList.DFALearnerVariantList; diff --git a/algorithms/active/pom.xml b/algorithms/active/pom.xml index bcd019b9de..5e225792c1 100644 --- a/algorithms/active/pom.xml +++ b/algorithms/active/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/active/ttt-vpda/pom.xml b/algorithms/active/ttt-vpda/pom.xml index 2660aa122b..4b4ea8ab09 100644 --- a/algorithms/active/ttt-vpda/pom.xml +++ b/algorithms/active/ttt-vpda/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -60,6 +60,11 @@ limitations under the License. automata-api + + org.checkerframework + checker-qual + + org.slf4j slf4j-api diff --git a/algorithms/active/ttt-vpda/src/main/java/de/learnlib/algorithms/ttt/vpda/TTTLearnerVPDA.java b/algorithms/active/ttt-vpda/src/main/java/de/learnlib/algorithms/ttt/vpda/TTTLearnerVPDA.java index c482281d60..f29fc71c94 100644 --- a/algorithms/active/ttt-vpda/src/main/java/de/learnlib/algorithms/ttt/vpda/TTTLearnerVPDA.java +++ b/algorithms/active/ttt-vpda/src/main/java/de/learnlib/algorithms/ttt/vpda/TTTLearnerVPDA.java @@ -43,6 +43,7 @@ import net.automatalib.automata.vpda.State; import net.automatalib.words.VPDAlphabet; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -162,11 +163,14 @@ private void splitState(OutputInconsistency outIncons) { Word suffix = acexSuffix.subWord(breakpoint + 1); State> state = hypothesis.getSuccessor(acex.getBaseState(), prefix); + assert state != null; State> succState = hypothesis.getSuccessor(state, act); + assert succState != null; ContextPair context = new ContextPair<>(transformAccessSequence(succState.getStackContents()), suffix); AbstractHypTrans trans = hypothesis.getInternalTransition(state, act); + assert trans != null; HypLoc newLoc = makeTree(trans); DTNode oldDtNode = succState.getLocation().getLeaf(); @@ -192,7 +196,7 @@ protected boolean finalizeAny() { return false; } - private OutputInconsistency findOutputInconsistency() { + private @Nullable OutputInconsistency findOutputInconsistency() { OutputInconsistency best = null; for (HypLoc loc : hypothesis.getLocations()) { @@ -216,6 +220,7 @@ private OutputInconsistency findOutputInconsistency() { protected State> getAnySuccessor(State> state, I sym) { final VPDAlphabet.SymbolType type = alphabet.getSymbolType(sym); + final StackContents stackContents = state.getStackContents(); switch (type) { case INTERNAL: { @@ -226,23 +231,23 @@ protected State> getAnySuccessor(State> state, I sym) { } else { succLoc = trans.getNonTreeTarget().subtreeLocsIterator().next(); } - return new State<>(succLoc, state.getStackContents()); + return new State<>(succLoc, stackContents); } case CALL: { int stackSym = hypothesis.encodeStackSym(state.getLocation(), sym); - return new State<>(hypothesis.getInitialLocation(), - StackContents.push(stackSym, state.getStackContents())); + return new State<>(hypothesis.getInitialLocation(), StackContents.push(stackSym, stackContents)); } case RETURN: { + assert stackContents != null; AbstractHypTrans trans = - hypothesis.getReturnTransition(state.getLocation(), sym, state.getStackContents().peek()); + hypothesis.getReturnTransition(state.getLocation(), sym, stackContents.peek()); HypLoc succLoc; if (trans.isTree()) { succLoc = trans.getTreeTarget(); } else { succLoc = trans.getNonTreeTarget().subtreeLocsIterator().next(); } - return new State<>(succLoc, state.getStackContents().pop()); + return new State<>(succLoc, stackContents.pop()); } default: throw new IllegalStateException("Unhandled type " + type); @@ -264,7 +269,7 @@ protected PrefixTransformAcex deriveAcex(OutputInconsistency outIncons) { * * @return a splitter for any of the blocks */ - private GlobalSplitter findSplitterGlobal() { + private @Nullable GlobalSplitter findSplitterGlobal() { DTNode bestBlockRoot = null; Splitter bestSplitter = null; @@ -326,7 +331,7 @@ private void finalizeDiscriminator(DTNode blockRoot, Splitter splitter) { * * @return a splitter for this block, or {@code null} if no such splitter could be found. */ - private Splitter findSplitter(DTNode blockRoot) { + private @Nullable Splitter findSplitter(DTNode blockRoot) { int alphabetSize = alphabet.getNumInternals() + alphabet.getNumCalls() * alphabet.getNumReturns() * hypothesis.size() * 2; @@ -623,6 +628,7 @@ protected void determinize(State> state, Word suffix) { for (I sym : suffix) { if (!alphabet.isCallSymbol(sym)) { AbstractHypTrans trans = hypothesis.getInternalTransition(curr, sym); + assert trans != null; if (!trans.isTree() && !trans.getNonTreeTarget().isLeaf()) { updateDTTargets(Collections.singletonList(trans), true); } diff --git a/algorithms/active/ttt/pom.xml b/algorithms/active/ttt/pom.xml index 76c89f884f..6b0785ff3d 100644 --- a/algorithms/active/ttt/pom.xml +++ b/algorithms/active/ttt/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-active-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/base/AbstractTTTLearner.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/base/AbstractTTTLearner.java index 512096d1fe..17a93a78a8 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/base/AbstractTTTLearner.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/base/AbstractTTTLearner.java @@ -54,6 +54,7 @@ * * @author Malte Isberner */ +@SuppressWarnings("PMD.ExcessiveClassLength") public abstract class AbstractTTTLearner implements LearningAlgorithm, SupportsGrowingAlphabet, Resumable> { diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/PrefixTTTLearnerDFA.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/PrefixTTTLearnerDFA.java index 6ac0129c55..7429aede86 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/PrefixTTTLearnerDFA.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/PrefixTTTLearnerDFA.java @@ -71,7 +71,8 @@ protected boolean refineHypothesisSingle(DefaultQuery ceQuery) { I sym = ceWord.getSymbol(breakpoint); Word newDiscr = lca.getDiscriminator().prepend(sym); ExtDTNode succHyp = acex.getHypNode(breakpoint + 1); - boolean hypOut = lca.subtreeLabel(succHyp); + Boolean hypOut = lca.subtreeLabel(succHyp); + assert hypOut != null; openTransitions.insertAllIncoming(toSplit.getIncoming()); ExtDTNode.SplitResult splitResult = toSplit.split(newDiscr, hypOut, !hypOut); link(splitResult.nodeOld, splitState); @@ -187,6 +188,7 @@ private final class EasyTTTPrefAcex implements AbstractCounterexample { public void update(int len) { TTTStateDFA curr = (TTTStateDFA) hypothesis.getInitialState(); + assert curr != null; hypNodes.set(0, (ExtDTNode) curr.getDTLeaf()); siftNodes.set(0, (ExtDTNode) curr.getDTLeaf()); diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/TTTLearnerDFA.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/TTTLearnerDFA.java index 09235ba400..f715d54c0d 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/TTTLearnerDFA.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/dfa/TTTLearnerDFA.java @@ -63,7 +63,9 @@ protected void initializeState(TTTState state) { super.initializeState(state); TTTStateDFA dfaState = (TTTStateDFA) state; - dfaState.accepting = dtree.getRoot().subtreeLabel(dfaState.getDTLeaf()); + Boolean aBoolean = dtree.getRoot().subtreeLabel(dfaState.getDTLeaf()); + assert aBoolean != null; + dfaState.accepting = aBoolean; } @Override diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/mealy/TTTLearnerMealy.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/mealy/TTTLearnerMealy.java index 2f06b49038..0aa8af9c0e 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/mealy/TTTLearnerMealy.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/mealy/TTTLearnerMealy.java @@ -118,7 +118,9 @@ protected Word predictSuccOutcome(TTTTransition> trans, if (succSeparator == null) { return Word.fromLetter(mtrans.output); } - return succSeparator.subtreeLabel(trans.getDTTarget()).prepend(mtrans.output); + Word subtreeLabel = succSeparator.subtreeLabel(trans.getDTTarget()); + assert subtreeLabel != null; + return subtreeLabel.prepend(mtrans.output); } @Override diff --git a/algorithms/passive/pom.xml b/algorithms/passive/pom.xml index 91a7ea7f36..a02dad8031 100644 --- a/algorithms/passive/pom.xml +++ b/algorithms/passive/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/passive/rpni-edsm/pom.xml b/algorithms/passive/rpni-edsm/pom.xml index d8119f127e..dfa02a9746 100644 --- a/algorithms/passive/rpni-edsm/pom.xml +++ b/algorithms/passive/rpni-edsm/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-passive-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/passive/rpni-edsm/src/main/java/de/learnlib/algorithms/rpni/BlueFringeEDSMDFA.java b/algorithms/passive/rpni-edsm/src/main/java/de/learnlib/algorithms/rpni/BlueFringeEDSMDFA.java index d47696ab93..032047a348 100644 --- a/algorithms/passive/rpni-edsm/src/main/java/de/learnlib/algorithms/rpni/BlueFringeEDSMDFA.java +++ b/algorithms/passive/rpni-edsm/src/main/java/de/learnlib/algorithms/rpni/BlueFringeEDSMDFA.java @@ -79,12 +79,14 @@ public DFA computeModel() { while (blueIter.hasNext()) { final PTATransition> qbRef = blueIter.next(); final BlueFringePTAState qb = qbRef.getTarget(); + assert qb != null; Stream> stream = pta.redStatesStream(); if (super.parallel) { stream = stream.parallel(); } + @SuppressWarnings("nullness") // we filter the null merges final Optional>, Long>> result = stream.map(qr -> tryMerge(pta, qr, qb)) .filter(Objects::nonNull) @@ -110,6 +112,7 @@ public DFA computeModel() { } } if (!promotion) { + assert bestMerge != null; blue.remove(bestTransition); bestMerge.apply(pta, blue::add); } diff --git a/algorithms/passive/rpni-edsm/src/test/java/de/learnlib/algorithms/rpni/EDSMScoreTest.java b/algorithms/passive/rpni-edsm/src/test/java/de/learnlib/algorithms/rpni/EDSMScoreTest.java index 400fba052b..92ecf57ce5 100644 --- a/algorithms/passive/rpni-edsm/src/test/java/de/learnlib/algorithms/rpni/EDSMScoreTest.java +++ b/algorithms/passive/rpni-edsm/src/test/java/de/learnlib/algorithms/rpni/EDSMScoreTest.java @@ -21,7 +21,7 @@ import de.learnlib.datastructure.pta.pta.BlueFringePTA; import de.learnlib.datastructure.pta.pta.BlueFringePTAState; -import net.automatalib.automata.UniversalDeterministicAutomaton; +import de.learnlib.datastructure.pta.pta.RedBlueMerge; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; @@ -70,14 +70,20 @@ public void testValue() { final BlueFringePTAState qA = pta.getState(Word.fromSymbols(0)); final BlueFringePTAState qB = pta.getState(Word.fromSymbols(1)); - final UniversalDeterministicAutomaton, Integer, ?, Boolean, Void> firstMerge = - pta.tryMerge(qEpsilon, qB).toMergedAutomaton(); - final UniversalDeterministicAutomaton, Integer, ?, Boolean, Void> - secondMerged = pta.tryMerge(qA, qB).toMergedAutomaton(); + final RedBlueMerge> merge1 = pta.tryMerge(qEpsilon, qB); + Assert.assertNotNull(merge1); + Assert.assertEquals(2L, + EDSMUtil.score(merge1.toMergedAutomaton(), + positiveSamplesAsIntArray, + negativeSamplesAsIntArray)); - Assert.assertEquals(2L, EDSMUtil.score(firstMerge, positiveSamplesAsIntArray, negativeSamplesAsIntArray)); + final RedBlueMerge> merge2 = pta.tryMerge(qA, qB); + Assert.assertNotNull(merge2); // book is wrong, should be 2 - Assert.assertEquals(2L, EDSMUtil.score(secondMerged, positiveSamplesAsIntArray, negativeSamplesAsIntArray)); + Assert.assertEquals(2L, + EDSMUtil.score(merge2.toMergedAutomaton(), + positiveSamplesAsIntArray, + negativeSamplesAsIntArray)); } /* @@ -105,7 +111,10 @@ private BlueFringePTA initializePTA() { pta.init((q) -> {}); pta.promote(qA, (q) -> {}); - pta.tryMerge(qEpsilon, qAA).apply(pta, (q) -> {}); + final RedBlueMerge> merge = pta.tryMerge(qEpsilon, qAA); + Assert.assertNotNull(merge); + + merge.apply(pta, (q) -> {}); return pta; } diff --git a/algorithms/passive/rpni-mdl/pom.xml b/algorithms/passive/rpni-mdl/pom.xml index 72443cc579..dcbfa3d8d4 100644 --- a/algorithms/passive/rpni-mdl/pom.xml +++ b/algorithms/passive/rpni-mdl/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-algorithms-passive-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/algorithms/passive/rpni-mdl/src/main/java/de/learnlib/algorithms/rpni/MDLUtil.java b/algorithms/passive/rpni-mdl/src/main/java/de/learnlib/algorithms/rpni/MDLUtil.java index be1111c6dc..99dc992876 100644 --- a/algorithms/passive/rpni-mdl/src/main/java/de/learnlib/algorithms/rpni/MDLUtil.java +++ b/algorithms/passive/rpni-mdl/src/main/java/de/learnlib/algorithms/rpni/MDLUtil.java @@ -42,11 +42,13 @@ private static double countWordChoices(UniversalDeterministicAutomaton de.learnlib learnlib-algorithms-passive-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -55,6 +55,11 @@ limitations under the License. automata-core + + org.checkerframework + checker-qual + + com.github.misberner.buildergen diff --git a/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/rpni/AbstractBlueFringeRPNI.java b/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/rpni/AbstractBlueFringeRPNI.java index d005551a39..6dc6c5ca4e 100644 --- a/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/rpni/AbstractBlueFringeRPNI.java +++ b/algorithms/passive/rpni/src/main/java/de/learnlib/algorithms/rpni/AbstractBlueFringeRPNI.java @@ -28,6 +28,7 @@ import de.learnlib.datastructure.pta.pta.PTATransition; import de.learnlib.datastructure.pta.pta.RedBlueMerge; import net.automatalib.words.Alphabet; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Abstract base class for Blue-Fringe-RPNI algorithms. @@ -114,12 +115,14 @@ public M computeModel() { PTATransition> qbRef; while ((qbRef = blue.poll()) != null) { BlueFringePTAState qb = qbRef.getTarget(); + assert qb != null; Stream> stream = pta.redStatesStream(); if (parallel) { stream = stream.parallel(); } + @SuppressWarnings("nullness") // we filter the null merges Stream>> filtered = stream.map(qr -> tryMerge(pta, qr, qb)).filter(Objects::nonNull).filter(this::decideOnValidMerge); @@ -158,9 +161,9 @@ public M computeModel() { * @return a valid {@link RedBlueMerge} object representing a possible merge of {@code qb} into {@code qr}, or * {@code null} if the merge is impossible */ - protected RedBlueMerge> tryMerge(BlueFringePTA pta, - BlueFringePTAState qr, - BlueFringePTAState qb) { + protected @Nullable RedBlueMerge> tryMerge(BlueFringePTA pta, + BlueFringePTAState qr, + BlueFringePTAState qb) { return pta.tryMerge(qr, qb); } diff --git a/algorithms/pom.xml b/algorithms/pom.xml index 61e3d45a95..15bcdbcb8e 100644 --- a/algorithms/pom.xml +++ b/algorithms/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/api/pom.xml b/api/pom.xml index 51e0d298f7..05c258a593 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/api/src/main/java/de/learnlib/api/exception/SULException.java b/api/src/main/java/de/learnlib/api/exception/SULException.java index 0485b03b88..4696ca98f2 100644 --- a/api/src/main/java/de/learnlib/api/exception/SULException.java +++ b/api/src/main/java/de/learnlib/api/exception/SULException.java @@ -17,6 +17,7 @@ import de.learnlib.api.SUL; import de.learnlib.api.oracle.MembershipOracle; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Unchecked exception class that can be used by implementors of a {@link SUL} to wrap any exceptions that occur during @@ -45,7 +46,7 @@ public class SULException extends RuntimeException { * @param cause * the exception cause, should never be a subclass of {@link Error}. */ - public SULException(Throwable cause) { + public SULException(@Nullable Throwable cause) { super(cause); } diff --git a/api/src/main/java/de/learnlib/api/oracle/AutomatonOracle.java b/api/src/main/java/de/learnlib/api/oracle/AutomatonOracle.java index 8e63857aca..7ffdc4e34e 100644 --- a/api/src/main/java/de/learnlib/api/oracle/AutomatonOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/AutomatonOracle.java @@ -50,7 +50,7 @@ public interface AutomatonOracle, I, D * * @return whether the given input and output is a counter example. */ - boolean isCounterExample(A hypothesis, Iterable inputs, @Nullable D output); + boolean isCounterExample(A hypothesis, Iterable inputs, D output); /** * Returns the next input word, or {@code null} if there is no next input. diff --git a/api/src/main/java/de/learnlib/api/oracle/LassoEmptinessOracle.java b/api/src/main/java/de/learnlib/api/oracle/LassoEmptinessOracle.java index b9b9bf12be..f2d9ccce66 100644 --- a/api/src/main/java/de/learnlib/api/oracle/LassoEmptinessOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/LassoEmptinessOracle.java @@ -33,18 +33,6 @@ */ public interface LassoEmptinessOracle, I, D> extends EmptinessOracle { - /** - * Return that when a lasso is ultimately periodic, it could serve as a counter example. - * - * @param isUltimatelyPeriodic - * whether the lasso is ultimately periodic - * - * @return true when a lasso is ultimately periodic, false otherwise. - */ - default boolean isOmegaCounterExample(boolean isUltimatelyPeriodic) { - return isUltimatelyPeriodic; - } - interface DFALassoEmptinessOracle extends LassoEmptinessOracle, I, Boolean> {} interface MealyLassoEmptinessOracle extends LassoEmptinessOracle, I, Word> {} diff --git a/api/src/main/java/de/learnlib/api/oracle/LassoOracle.java b/api/src/main/java/de/learnlib/api/oracle/LassoOracle.java index f895cec1cf..1c1039aa99 100644 --- a/api/src/main/java/de/learnlib/api/oracle/LassoOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/LassoOracle.java @@ -67,16 +67,6 @@ public interface LassoOracle, I, D> { */ boolean isCounterExample(Output hypothesis, Iterable inputs, D output); - /** - * Returns whether a lasso that is ultimately periodic could serve as a counter example. - * - * @param isUltimatelyPeriodic - * whether the lasso is ultimately periodic - * - * @return true when lasso that is ultimately periodic could serve as a counter example, false otherwise. - */ - boolean isOmegaCounterExample(boolean isUltimatelyPeriodic); - default @Nullable DefaultQuery findCounterExample(L hypothesis, Collection inputs) { final Word prefix = hypothesis.getPrefix(); final Word loop = hypothesis.getLoop(); @@ -84,15 +74,16 @@ public interface LassoOracle, I, D> { final OmegaQuery omegaQuery = processInput(prefix, loop, repeat); - final DefaultQuery query; - if (isOmegaCounterExample(omegaQuery.isUltimatelyPeriodic())) { + if (omegaQuery.isUltimatelyPeriodic()) { + @SuppressWarnings("nullness") // when we are a counterexample, the output is valid final DefaultQuery ce = omegaQuery.asDefaultQuery(); - query = isCounterExample(hypothesis.getAutomaton(), ce.getInput(), ce.getOutput()) ? ce : null; - } else { - query = null; + + if (isCounterExample(hypothesis.getAutomaton(), ce.getInput(), ce.getOutput())) { + return ce; + } } - return query; + return null; } interface DFALassoOracle extends LassoOracle, I, Boolean> {} diff --git a/api/src/main/java/de/learnlib/api/oracle/MembershipOracle.java b/api/src/main/java/de/learnlib/api/oracle/MembershipOracle.java index 9b04609767..13be1c56bb 100644 --- a/api/src/main/java/de/learnlib/api/oracle/MembershipOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/MembershipOracle.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.Collections; +import de.learnlib.api.oracle.parallelism.BatchProcessor; import de.learnlib.api.query.DefaultQuery; import de.learnlib.api.query.Query; import net.automatalib.words.Word; @@ -32,7 +33,7 @@ * @author Maik Merten * @see DefaultQuery */ -public interface MembershipOracle extends QueryAnswerer { +public interface MembershipOracle extends QueryAnswerer, BatchProcessor> { @Override default D answerQuery(Word input) { @@ -78,6 +79,11 @@ default MembershipOracle asOracle() { return this; } + @Override + default void processBatch(Collection> batch) { + processQueries(batch); + } + interface DFAMembershipOracle extends MembershipOracle {} /** diff --git a/api/src/main/java/de/learnlib/api/oracle/OmegaMembershipOracle.java b/api/src/main/java/de/learnlib/api/oracle/OmegaMembershipOracle.java index 385754b90f..f34623d808 100644 --- a/api/src/main/java/de/learnlib/api/oracle/OmegaMembershipOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/OmegaMembershipOracle.java @@ -21,9 +21,11 @@ import de.learnlib.api.ObservableSUL; import de.learnlib.api.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.api.oracle.MembershipOracle.MealyMembershipOracle; +import de.learnlib.api.oracle.parallelism.BatchProcessor; import de.learnlib.api.query.OmegaQuery; import net.automatalib.commons.util.Pair; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Answers {@link OmegaQuery}s, similar to a {@link MembershipOracle}. Additionally, one can ask whether two states @@ -35,15 +37,20 @@ * @param the input type * @param the output type */ -public interface OmegaMembershipOracle extends OmegaQueryAnswerer { +public interface OmegaMembershipOracle extends OmegaQueryAnswerer, BatchProcessor> { @Override - default Pair answerQuery(Word prefix, Word loop, int repeat) { + default Pair<@Nullable D, Integer> answerQuery(Word prefix, Word loop, int repeat) { final OmegaQuery query = new OmegaQuery<>(prefix, loop, repeat); processQuery(query); return Pair.of(query.getOutput(), query.getPeriodicity()); } + @Override + default void processBatch(Collection> batch) { + this.processQueries(batch); + } + default void processQuery(OmegaQuery query) { processQueries(Collections.singleton(query)); } diff --git a/api/src/main/java/de/learnlib/api/oracle/OmegaQueryAnswerer.java b/api/src/main/java/de/learnlib/api/oracle/OmegaQueryAnswerer.java index 872830ae90..0e95f3342c 100644 --- a/api/src/main/java/de/learnlib/api/oracle/OmegaQueryAnswerer.java +++ b/api/src/main/java/de/learnlib/api/oracle/OmegaQueryAnswerer.java @@ -17,6 +17,7 @@ import net.automatalib.commons.util.Pair; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Answers {@link de.learnlib.api.query.OmegaQuery}s. @@ -27,7 +28,7 @@ */ public interface OmegaQueryAnswerer { - Pair answerQuery(Word prefix, Word loop, int repeat); + Pair<@Nullable D, Integer> answerQuery(Word prefix, Word loop, int repeat); OmegaMembershipOracle asOracle(); } diff --git a/api/src/main/java/de/learnlib/api/oracle/SingleQueryOmegaOracle.java b/api/src/main/java/de/learnlib/api/oracle/SingleQueryOmegaOracle.java index 90133c5975..9e8ef526fa 100644 --- a/api/src/main/java/de/learnlib/api/oracle/SingleQueryOmegaOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/SingleQueryOmegaOracle.java @@ -20,6 +20,7 @@ import de.learnlib.api.query.OmegaQuery; import net.automatalib.commons.util.Pair; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An {@link OmegaMembershipOracle} that answers single queries. @@ -33,7 +34,7 @@ public interface SingleQueryOmegaOracle extends OmegaMembershipOracle query) { - Pair output = answerQuery(query.getPrefix(), query.getLoop(), query.getRepeat()); + Pair<@Nullable D, Integer> output = answerQuery(query.getPrefix(), query.getLoop(), query.getRepeat()); query.answer(output.getFirst(), output.getSecond()); } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleInterruptedException.java b/api/src/main/java/de/learnlib/api/oracle/parallelism/BatchInterruptedException.java similarity index 65% rename from oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleInterruptedException.java rename to api/src/main/java/de/learnlib/api/oracle/parallelism/BatchInterruptedException.java index 4be37c5ac5..641106dc7b 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleInterruptedException.java +++ b/api/src/main/java/de/learnlib/api/oracle/parallelism/BatchInterruptedException.java @@ -13,22 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.learnlib.oracle.parallelism; +package de.learnlib.api.oracle.parallelism; -import de.learnlib.api.oracle.MembershipOracle; +import java.util.Collection; /** - * Exception that is thrown if a parallel oracle is interrupted during execution. Note that we cannot rethrow the {@link - * InterruptedException} since the {@code throws} specification of {@link MembershipOracle#processQueries(java.util.Collection)} - * does not allow doing so. + * Exception that is thrown if a parallel batch is interrupted during processing. Note that we cannot rethrow the {@link + * InterruptedException} since the {@code throws} specification of {@link BatchProcessor#processBatch(Collection)} does + * not allow doing so. * * @author Malte Isberner */ -public class ParallelOracleInterruptedException extends RuntimeException { +public class BatchInterruptedException extends RuntimeException { private static final long serialVersionUID = 1L; - public ParallelOracleInterruptedException(Throwable cause) { + public BatchInterruptedException(Throwable cause) { super(cause); } diff --git a/api/src/main/java/de/learnlib/api/oracle/parallelism/BatchProcessor.java b/api/src/main/java/de/learnlib/api/oracle/parallelism/BatchProcessor.java new file mode 100644 index 0000000000..dee7408e05 --- /dev/null +++ b/api/src/main/java/de/learnlib/api/oracle/parallelism/BatchProcessor.java @@ -0,0 +1,44 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.api.oracle.parallelism; + +import java.util.Collection; + +import de.learnlib.api.oracle.MembershipOracle; + +/** + * A markup interface for classes that can process a batch of work in a parallel environment (e.g. a {@link + * MembershipOracle} when used by a {@link ParallelOracle}). + * + * @param + * batch type + * + * @author frohme + */ +public interface BatchProcessor { + + /** + * Process the batch. + * + * @param batch + * the batch to process + * + * @throws BatchInterruptedException + * if the processing thread was interrupted by an exception. + */ + void processBatch(Collection batch); + +} diff --git a/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOmegaOracle.java b/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOmegaOracle.java new file mode 100644 index 0000000000..70cc0bff3d --- /dev/null +++ b/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOmegaOracle.java @@ -0,0 +1,32 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.api.oracle.parallelism; + +import de.learnlib.api.oracle.OmegaMembershipOracle; + +/** + * {@link ParallelOracle} equivalent for {@link OmegaMembershipOracle}. + * + * @param + * oracle state type + * @param + * input symbol type + * @param + * output domain type + * + * @author frohme + */ +public interface ParallelOmegaOracle extends ThreadPool, OmegaMembershipOracle {} diff --git a/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOracle.java b/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOracle.java new file mode 100644 index 0000000000..98fc74874f --- /dev/null +++ b/api/src/main/java/de/learnlib/api/oracle/parallelism/ParallelOracle.java @@ -0,0 +1,34 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.api.oracle.parallelism; + +import de.learnlib.api.oracle.MembershipOracle; + +/** + * Basic interface for {@link MembershipOracle}s that can process queries in parallel. + *

+ * Parallel oracles usually use one or more dedicated worker threads in which the processing of queries is performed. + * Since these do not have a defined life span, they must be terminated explicitly using {@link #shutdown()} or {@link + * #shutdownNow()}. + * + * @param + * input symbol type + * @param + * output domain type + * + * @author Malte Isberner + */ +public interface ParallelOracle extends ThreadPool, MembershipOracle {} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracle.java b/api/src/main/java/de/learnlib/api/oracle/parallelism/ThreadPool.java similarity index 74% rename from oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracle.java rename to api/src/main/java/de/learnlib/api/oracle/parallelism/ThreadPool.java index f91481654b..7357b942a0 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracle.java +++ b/api/src/main/java/de/learnlib/api/oracle/parallelism/ThreadPool.java @@ -13,28 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.learnlib.oracle.parallelism; +package de.learnlib.api.oracle.parallelism; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import de.learnlib.api.oracle.MembershipOracle; - /** - * Basic interface for {@link MembershipOracle}s that can process queries in parallel. - *

- * Parallel oracles usually use one or more dedicated worker threads in which the processing of queries is performed. - * Since these do not have a defined life span, they must be terminated explicitly using {@link #shutdown()} or {@link - * #shutdownNow()}. - * - * @param - * input symbol type - * @param - * output domain type + * A markup interface for a component that manages a pool of threads that may want to be shut down after usage. * * @author Malte Isberner + * @author frohme + * @see ExecutorService */ -public interface ParallelOracle extends MembershipOracle { +public interface ThreadPool { /** * Shuts down all worker threads, but waits for any queued queries to be processed. @@ -58,7 +49,7 @@ public interface ParallelOracle extends MembershipOracle { enum PoolPolicy { /** * Maintain a fixed thread pool. The threads will be started immediately, and will terminate only if {@link - * ParallelOracle#shutdown()} or {@link ParallelOracle#shutdownNow()} are called. + * ThreadPool#shutdown()} or {@link ThreadPool#shutdownNow()} are called. * * @see Executors#newFixedThreadPool(int) */ diff --git a/api/src/main/java/de/learnlib/api/query/OmegaQuery.java b/api/src/main/java/de/learnlib/api/query/OmegaQuery.java index 566777c621..ffe46bb228 100644 --- a/api/src/main/java/de/learnlib/api/query/OmegaQuery.java +++ b/api/src/main/java/de/learnlib/api/query/OmegaQuery.java @@ -49,7 +49,7 @@ public class OmegaQuery { private final Word loop; private final int repeat; - private D output; + private @Nullable D output; private int periodicity; public OmegaQuery(Word prefix, Word loop, int repeat) { @@ -58,7 +58,7 @@ public OmegaQuery(Word prefix, Word loop, int repeat) { this.repeat = repeat; } - public void answer(D output, int periodicity) { + public void answer(@Nullable D output, int periodicity) { this.output = output; this.periodicity = periodicity; } @@ -75,7 +75,7 @@ public int getRepeat() { return repeat; } - public D getOutput() { + public @Nullable D getOutput() { return output; } @@ -87,7 +87,7 @@ public boolean isUltimatelyPeriodic() { return periodicity > 0; } - public DefaultQuery asDefaultQuery() { + public DefaultQuery asDefaultQuery() { final WordBuilder wb = new WordBuilder<>(prefix.length() + loop.length() * periodicity); wb.append(prefix); wb.repeatAppend(periodicity, loop); diff --git a/api/src/main/java/de/learnlib/api/query/Query.java b/api/src/main/java/de/learnlib/api/query/Query.java index 6292e51d29..7590b2b0ec 100644 --- a/api/src/main/java/de/learnlib/api/query/Query.java +++ b/api/src/main/java/de/learnlib/api/query/Query.java @@ -75,7 +75,8 @@ public abstract class Query { * throwing an exception. * * @param output - * the output, i.e., the response to the query + * the output, i.e., the directly observable response to the query's suffix (cf. {@link Query main + * documentation}) */ public abstract void answer(D output); diff --git a/archetypes/basic/pom.xml b/archetypes/basic/pom.xml index caeedce7a1..9bb9e77c64 100644 --- a/archetypes/basic/pom.xml +++ b/archetypes/basic/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib.archetypes learnlib-archetypes-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/archetypes/complete/pom.xml b/archetypes/complete/pom.xml index 2564ff8dd1..545c25dc25 100644 --- a/archetypes/complete/pom.xml +++ b/archetypes/complete/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib.archetypes learnlib-archetypes-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/archetypes/pom.xml b/archetypes/pom.xml index 3425565441..839f018e3e 100644 --- a/archetypes/pom.xml +++ b/archetypes/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 66b560264e..2f6d6dadbb 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -148,37 +148,6 @@ limitations under the License. - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack-pmd - verify - - unpack - - - de.learnlib:learnlib-build-config:${project.version} - learnlib-pmd-exclusions.properties - ${project.build.directory}/pmd/ - - - - org.apache.maven.plugins maven-pmd-plugin @@ -189,12 +158,6 @@ limitations under the License. check - - - - ${project.build.directory}/pmd/learnlib-pmd-exclusions.properties - - @@ -216,17 +179,11 @@ limitations under the License. cf + - org.checkerframework - checker - ${checkerframework.version} - provided - - - org.checkerframework - jdk8 - ${checkerframework.version} - provided + com.google.code.findbugs + jsr305 + ${jsr305.version} com.google.errorprone @@ -259,21 +216,35 @@ limitations under the License. ${learnlib.java9OrNewer} true - only true ${project.build.directory}/checkerframework + + + org.checkerframework + checker + ${checkerframework.version} + + org.checkerframework.checker.nullness.NullnessChecker -J-Xbootclasspath/p:${com.google.errorprone:javac:jar} - -Xbootclasspath/p:${org.checkerframework:jdk8:jar} - - -AonlyDefs=^de\.learnlib - -AskipUses=.* + -AskipDefs=^de.learnlib.algorithms.adt.*|\ + ^de.learnlib.algorithms.discriminationtree.*.vpda.*|\ + ^de.learnlib.algorithms.ttt.*|\ + ^de.learnlib.datastructure.discriminationtree.*|\ + + -AskipUses=^de.learnlib.algorithms.adt.*|\ + ^de.learnlib.algorithms.ttt.*|\ + ^de.learnlib.datastructure.discriminationtree.*|\ + ^java.lang.ThreadLocal|\ + ^net.automatalib.*|\ + ^org.testng.*|\ + -AsuppressWarnings=uninitialized -AassumeAssertionsAreEnabled - + -Astubs=jdk8.astub:collection-object-parameters-may-be-null.astub @@ -286,20 +257,24 @@ limitations under the License. ${learnlib.java9OrNewer} true - only true ${project.build.directory}/checkerframework + + + org.checkerframework + checker + ${checkerframework.version} + + org.checkerframework.checker.nullness.NullnessChecker -J-Xbootclasspath/p:${com.google.errorprone:javac:jar} - -Xbootclasspath/p:${org.checkerframework:jdk8:jar} -AonlyDefs=^de\.learnlib -AskipUses=.* -AsuppressWarnings=uninitialized -AassumeAssertionsAreEnabled - -Alint=redundantNullComparison diff --git a/build-tools/annotation-processor/pom.xml b/build-tools/annotation-processor/pom.xml index 00ba228923..883f44b6b2 100644 --- a/build-tools/annotation-processor/pom.xml +++ b/build-tools/annotation-processor/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../../build-parent/pom.xml @@ -72,4 +72,16 @@ limitations under the License. + + + + cf + + + org.checkerframework + checker-qual + + + + diff --git a/build-tools/build-config/pom.xml b/build-tools/build-config/pom.xml index 188a9d33e7..5991b36b89 100644 --- a/build-tools/build-config/pom.xml +++ b/build-tools/build-config/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-tools-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/build-tools/build-config/src/main/resources/automatalib-learnlib-checkstyle.xml b/build-tools/build-config/src/main/resources/automatalib-learnlib-checkstyle.xml index be4e2f7299..3214f49a19 100644 --- a/build-tools/build-config/src/main/resources/automatalib-learnlib-checkstyle.xml +++ b/build-tools/build-config/src/main/resources/automatalib-learnlib-checkstyle.xml @@ -130,9 +130,9 @@ limitations under the License. - + + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, INSTANCE_INIT"/> diff --git a/build-tools/build-config/src/main/resources/learnlib-pmd-exclusions.properties b/build-tools/build-config/src/main/resources/learnlib-pmd-exclusions.properties deleted file mode 100644 index 590d7b196e..0000000000 --- a/build-tools/build-config/src/main/resources/learnlib-pmd-exclusions.properties +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2013-2020 TU Dortmund -# This file is part of LearnLib, http://www.learnlib.de/. -# -# 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. - - -# expected behavior, we simply want to ignore it and re-start searching -de.learnlib.algorithms.adt.config.model.extender.DefaultExtender=EmptyCatchBlock - -# we want to establish the contract of a (insertion-) stable set -de.learnlib.algorithms.dhc.mealy.MealyDHC=LooseCoupling -de.learnlib.algorithms.dhc.mealy.MealyDHCState=LooseCoupling - -# ignore exception -de.learnlib.algorithms.ttt.base.AbstractTTTLearner=EmptyCatchBlock,ExcessiveClassLength - -# RuntimeExceptions are the type of exceptions we allow to handle, therefore we should throw them -de.learnlib.drivers.reflect.ConcreteMethodInput=AvoidThrowingRawExceptionTypes,PreserveStackTrace -de.learnlib.drivers.reflect.SimplePOJODataMapper=AvoidThrowingRawExceptionTypes,PreserveStackTrace - -# fine for exampels -de.learnlib.examples.Example1=SystemPrintln -de.learnlib.examples.Example2=SystemPrintln -de.learnlib.examples.Example3=SystemPrintln -de.learnlib.examples.sli.Example1=SystemPrintln -de.learnlib.examples.sli.Example2=SystemPrintln - -# we want to allow mapping generic RuntimeExceptions -de.learnlib.mapper.MappedSUL=AvoidCatchingGenericException -de.learnlib.mapper.SULMapperComposition=AvoidCatchingGenericException diff --git a/build-tools/build-config/src/main/resources/learnlib-spotbugs-exclusions.xml b/build-tools/build-config/src/main/resources/learnlib-spotbugs-exclusions.xml index 66cf819917..4e483b788e 100644 --- a/build-tools/build-config/src/main/resources/learnlib-spotbugs-exclusions.xml +++ b/build-tools/build-config/src/main/resources/learnlib-spotbugs-exclusions.xml @@ -35,15 +35,78 @@ limitations under the License. - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/build-tools/pom.xml b/build-tools/pom.xml index d82e56468e..bb9e2c956a 100644 --- a/build-tools/pom.xml +++ b/build-tools/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/commons/acex/pom.xml b/commons/acex/pom.xml index 28b271fd4a..4b0faecaa5 100644 --- a/commons/acex/pom.xml +++ b/commons/acex/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-commons-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -37,6 +37,11 @@ limitations under the License. automata-commons-smartcollections + + org.checkerframework + checker-qual + + org.testng diff --git a/commons/acex/src/main/java/de/learnlib/acex/impl/AbstractBaseCounterexample.java b/commons/acex/src/main/java/de/learnlib/acex/impl/AbstractBaseCounterexample.java index f6236122dc..e0980fe2f9 100644 --- a/commons/acex/src/main/java/de/learnlib/acex/impl/AbstractBaseCounterexample.java +++ b/commons/acex/src/main/java/de/learnlib/acex/impl/AbstractBaseCounterexample.java @@ -17,6 +17,7 @@ import de.learnlib.acex.AbstractCounterexample; import net.automatalib.commons.smartcollections.ArrayStorage; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; public abstract class AbstractBaseCounterexample implements AbstractCounterexample { @@ -54,7 +55,9 @@ public E effect(int index) { protected abstract E computeEffect(int index); - public void setEffect(int index, E effect) { + public void setEffect(@UnknownInitialization(AbstractBaseCounterexample.class) AbstractBaseCounterexample this, + int index, + E effect) { values.set(index, effect); } diff --git a/commons/counterexamples/pom.xml b/commons/counterexamples/pom.xml index 1eefd649b4..c408af7bc7 100644 --- a/commons/counterexamples/pom.xml +++ b/commons/counterexamples/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-commons-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/commons/pom.xml b/commons/pom.xml index 10e501011b..b5772ebc40 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/commons/settings/pom.xml b/commons/settings/pom.xml index e28a675ab2..716e911c7d 100644 --- a/commons/settings/pom.xml +++ b/commons/settings/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-commons-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/commons/settings/src/main/java/de/learnlib/setting/LearnLibProperty.java b/commons/settings/src/main/java/de/learnlib/setting/LearnLibProperty.java index 47324dc577..5335506336 100644 --- a/commons/settings/src/main/java/de/learnlib/setting/LearnLibProperty.java +++ b/commons/settings/src/main/java/de/learnlib/setting/LearnLibProperty.java @@ -50,15 +50,7 @@ public enum LearnLibProperty { *

* Size of thread pools for parallel oracles. */ - PARALLEL_POOL_SIZE("parallel.pool_size"), - - /** - * {@code learnlib.queries.parallel.threshold}. - *

- * If batch sizes exceed the specified threshold, they will be processed in parallel. - * (Note: This only covers processing of batches. They are still answered sequentially.) - */ - PARALLEL_QUERIES_THRESHOLD("queries.parallel.threshold"); + PARALLEL_POOL_SIZE("parallel.pool_size"); private final String key; diff --git a/commons/settings/src/test/java/de/learnlib/setting/LearnLibSettingsTest.java b/commons/settings/src/test/java/de/learnlib/setting/LearnLibSettingsTest.java index cc680b3bfe..64cb7000e2 100644 --- a/commons/settings/src/test/java/de/learnlib/setting/LearnLibSettingsTest.java +++ b/commons/settings/src/test/java/de/learnlib/setting/LearnLibSettingsTest.java @@ -53,9 +53,6 @@ public void testProperties() { case PARALLEL_POOL_SIZE: Assert.assertEquals(3, settings.getInt(LearnLibProperty.PARALLEL_POOL_SIZE, 0)); break; - case PARALLEL_QUERIES_THRESHOLD: - Assert.assertEquals(100, settings.getInt(LearnLibProperty.PARALLEL_QUERIES_THRESHOLD, 0)); - break; default: throw new IllegalStateException("Unhandled property " + p); } diff --git a/commons/settings/src/test/resources/learnlib.properties b/commons/settings/src/test/resources/learnlib.properties index cf8c485426..d1c21da905 100644 --- a/commons/settings/src/test/resources/learnlib.properties +++ b/commons/settings/src/test/resources/learnlib.properties @@ -1,5 +1,4 @@ learnlib.parallel.batch_size.dynamic=1 learnlib.parallel.batch_size.static=2 learnlib.parallel.pool_policy=CACHED -learnlib.parallel.pool_size=3 -learnlib.queries.parallel.threshold=100 \ No newline at end of file +learnlib.parallel.pool_size=3 \ No newline at end of file diff --git a/commons/util/pom.xml b/commons/util/pom.xml index 2b80c0abbe..a92c0ea70b 100644 --- a/commons/util/pom.xml +++ b/commons/util/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-commons-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -36,10 +36,6 @@ limitations under the License. de.learnlib learnlib-api - - de.learnlib - learnlib-settings - de.learnlib learnlib-statistics diff --git a/commons/util/src/main/java/de/learnlib/util/MQUtil.java b/commons/util/src/main/java/de/learnlib/util/MQUtil.java index 9a5b21349b..e3b496503f 100644 --- a/commons/util/src/main/java/de/learnlib/util/MQUtil.java +++ b/commons/util/src/main/java/de/learnlib/util/MQUtil.java @@ -24,21 +24,13 @@ import de.learnlib.api.query.DefaultQuery; import de.learnlib.api.query.OmegaQuery; import de.learnlib.api.query.Query; -import de.learnlib.setting.LearnLibProperty; -import de.learnlib.setting.LearnLibSettings; import net.automatalib.automata.concepts.SuffixOutput; import net.automatalib.commons.util.Pair; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; public final class MQUtil { - public static final int PARALLEL_THRESHOLD; - - static { - LearnLibSettings settings = LearnLibSettings.getInstance(); - PARALLEL_THRESHOLD = settings.getInt(LearnLibProperty.PARALLEL_QUERIES_THRESHOLD, -1); - } - private MQUtil() { // prevent instantiation } @@ -60,24 +52,6 @@ public static DefaultQuery query(MembershipOracle oracle, Wor return query(oracle, Word.epsilon(), queryWord); } - public static void answerQueriesAuto(QueryAnswerer answerer, - Collection> queries) { - if (PARALLEL_THRESHOLD < 0 || queries.size() < PARALLEL_THRESHOLD) { - answerQueries(answerer, queries); - } else { - answerQueriesParallel(answerer, queries); - } - } - - public static void answerOmegaQueriesAuto(OmegaQueryAnswerer answerer, - Collection> queries) { - if (PARALLEL_THRESHOLD < 0 || queries.size() < PARALLEL_THRESHOLD) { - answerOmegaQueries(answerer, queries); - } else { - answerOmegaQueriesParallel(answerer, queries); - } - } - public static void answerQueries(QueryAnswerer answerer, Collection> queries) { for (Query query : queries) { Word prefix = query.getPrefix(); @@ -93,32 +67,11 @@ public static void answerOmegaQueries(OmegaQueryAnswerer answ final Word prefix = query.getPrefix(); final Word loop = query.getLoop(); final int repeat = query.getRepeat(); - Pair answer = answerer.answerQuery(prefix, loop, repeat); + Pair<@Nullable D, Integer> answer = answerer.answerQuery(prefix, loop, repeat); query.answer(answer.getFirst(), answer.getSecond()); } } - public static void answerQueriesParallel(QueryAnswerer answerer, - Collection> queries) { - queries.parallelStream().forEach(q -> { - Word prefix = q.getPrefix(); - Word suffix = q.getSuffix(); - D answer = answerer.answerQuery(prefix, suffix); - q.answer(answer); - }); - } - - public static void answerOmegaQueriesParallel(OmegaQueryAnswerer answerer, - Collection> queries) { - queries.parallelStream().forEach(q -> { - final Word prefix = q.getPrefix(); - final Word loop = q.getLoop(); - final int repeat = q.getRepeat(); - Pair answer = answerer.answerQuery(prefix, loop, repeat); - q.answer(answer.getFirst(), answer.getSecond()); - }); - } - public static boolean isCounterexample(DefaultQuery query, SuffixOutput hyp) { D qryOut = query.getOutput(); D hypOut = hyp.computeSuffixOutput(query.getPrefix(), query.getSuffix()); diff --git a/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java b/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java index e298c88994..a2f8508c9f 100644 --- a/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java +++ b/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java @@ -122,7 +122,7 @@ private static int doFindMismatch(MealyMachine hypothes return new MealyLearnerWrapper<>(learner); } - public static MembershipOracle wrapWordOracle(MembershipOracle> oracle) { + public static MembershipOracle wrapWordOracle(MembershipOracle> oracle) { return new SymbolOracleWrapper<>(oracle); } diff --git a/commons/util/src/main/java/de/learnlib/util/mealy/SymbolOracleWrapper.java b/commons/util/src/main/java/de/learnlib/util/mealy/SymbolOracleWrapper.java index a18ac63e3f..9fe28eee8f 100644 --- a/commons/util/src/main/java/de/learnlib/util/mealy/SymbolOracleWrapper.java +++ b/commons/util/src/main/java/de/learnlib/util/mealy/SymbolOracleWrapper.java @@ -22,6 +22,7 @@ import de.learnlib.api.oracle.MembershipOracle; import de.learnlib.api.query.Query; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Word-to-Symbol-Oracle adapter. @@ -36,7 +37,7 @@ * * @author Malte Isberner */ -final class SymbolOracleWrapper implements MembershipOracle { +final class SymbolOracleWrapper implements MembershipOracle { private final MembershipOracle> wordOracle; @@ -51,9 +52,9 @@ final class SymbolOracleWrapper implements MembershipOracle { } @Override - public void processQueries(Collection> queries) { + public void processQueries(Collection> queries) { List> lsQueries = new ArrayList<>(queries.size()); - for (Query qry : queries) { + for (Query qry : queries) { lsQueries.add(new LastSymbolQuery<>(qry)); } @@ -62,9 +63,9 @@ public void processQueries(Collection> queries) { private static final class LastSymbolQuery extends Query> { - private final Query originalQuery; + private final Query originalQuery; - LastSymbolQuery(Query originalQuery) { + LastSymbolQuery(Query originalQuery) { this.originalQuery = originalQuery; } diff --git a/datastructures/discrimination-tree/pom.xml b/datastructures/discrimination-tree/pom.xml index cc2767e3d6..3d10ff9dcb 100644 --- a/datastructures/discrimination-tree/pom.xml +++ b/datastructures/discrimination-tree/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-datastructures-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -64,11 +64,6 @@ limitations under the License. automata-util - - org.checkerframework - checker-qual - - de.learnlib.testsupport diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/BinaryDTNode.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/BinaryDTNode.java index 5c9b8acdb8..a0c6469f19 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/BinaryDTNode.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/BinaryDTNode.java @@ -20,7 +20,6 @@ import de.learnlib.datastructure.discriminationtree.model.AbstractWordBasedDTNode; import de.learnlib.datastructure.discriminationtree.model.BooleanMap; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Binary discrimination tree node specialization. @@ -38,7 +37,7 @@ public BinaryDTNode(D data) { super(data); } - public BinaryDTNode(BinaryDTNode parent, Boolean parentOutcome, @Nullable D data) { + public BinaryDTNode(BinaryDTNode parent, Boolean parentOutcome, D data) { super(parent, parentOutcome, data); } @@ -48,7 +47,7 @@ protected Map> createChildMap() } @Override - protected BinaryDTNode createChild(Boolean outcome, @Nullable D data) { + protected BinaryDTNode createChild(Boolean outcome, D data) { return new BinaryDTNode<>(this, outcome, data); } } diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/MultiDTNode.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/MultiDTNode.java index 5d89a7a0cc..8fa0d3b64c 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/MultiDTNode.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/MultiDTNode.java @@ -19,7 +19,6 @@ import java.util.Map; import de.learnlib.datastructure.discriminationtree.model.AbstractWordBasedDTNode; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Generic n-ary discrimination tree node specialization. @@ -39,7 +38,7 @@ public MultiDTNode(D data) { super(data); } - public MultiDTNode(MultiDTNode parent, O parentOutcome, @Nullable D data) { + public MultiDTNode(MultiDTNode parent, O parentOutcome, D data) { super(parent, parentOutcome, data); } @@ -49,7 +48,7 @@ protected Map> createChildMap() { } @Override - protected MultiDTNode createChild(O outcome, @Nullable D data) { + protected MultiDTNode createChild(O outcome, D data) { return new MultiDTNode<>(this, outcome, data); } } diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractDTNode.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractDTNode.java index acc3dc35a9..90dda6e82b 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractDTNode.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractDTNode.java @@ -20,10 +20,6 @@ import java.util.Map; import java.util.Objects; -import org.checkerframework.checker.nullness.qual.EnsuresNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; - /** * An abstract super class (DAO) for aggregating several information stored in a node of an discrimination tree. * @@ -40,18 +36,18 @@ */ public abstract class AbstractDTNode> implements Serializable { - protected final @Nullable N parent; - protected final @Nullable O parentOutcome; + protected final N parent; + protected final O parentOutcome; protected final int depth; - protected @Nullable Map children; + protected Map children; protected DSCR discriminator; - protected @Nullable D data; + protected D data; public AbstractDTNode(D data) { this(null, null, data); } - protected AbstractDTNode(N parent, O parentOutcome, @Nullable D data) { + protected AbstractDTNode(N parent, O parentOutcome, D data) { this.parent = parent; this.parentOutcome = parentOutcome; this.depth = (parent != null) ? parent.depth + 1 : 0; @@ -78,8 +74,7 @@ public SplitResult split(DSCR discriminator, O oldOut, O newOut) { return this.split(discriminator, oldOut, newOut, null); } - @EnsuresNonNull("children") - public SplitResult split(DSCR discriminator, O oldOut, O newOut, @Nullable D newData) { + public SplitResult split(DSCR discriminator, O oldOut, O newOut, D newData) { assert this.isLeaf(); assert !Objects.equals(oldOut, newOut); @@ -100,20 +95,19 @@ public boolean isLeaf() { protected abstract Map createChildMap(); - @RequiresNonNull("children") - protected N addChild(O outcome, @Nullable D data) { + protected N addChild(O outcome, D data) { final N child = createChild(outcome, data); children.put(outcome, child); return child; } - protected abstract N createChild(O outcome, @Nullable D data); + protected abstract N createChild(O outcome, D data); public N child(O out) { return child(out, null); } - public N child(O out, @Nullable D defaultData) { + public N child(O out, D defaultData) { assert !isLeaf(); N result = getChild(out); @@ -123,17 +117,14 @@ public N child(O out, @Nullable D defaultData) { return result; } - @RequiresNonNull("children") public N getChild(O out) { return children.get(out); } - @RequiresNonNull("children") public Collection getChildren() { return children.values(); } - @RequiresNonNull("children") public Collection> getChildEntries() { return children.entrySet(); } @@ -156,7 +147,7 @@ public void setData(D data) { this.data = data; } - public @Nullable O subtreeLabel(N descendant) { + public O subtreeLabel(N descendant) { N curr = descendant; while (curr.depth > this.depth + 1) { @@ -170,7 +161,7 @@ public void setData(D data) { return curr.getParentOutcome(); } - public @Nullable O getParentOutcome() { + public O getParentOutcome() { return parentOutcome; } diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractTemporaryIntrusiveDTNode.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractTemporaryIntrusiveDTNode.java index 5b99fbdb4e..0f407b0c27 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractTemporaryIntrusiveDTNode.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractTemporaryIntrusiveDTNode.java @@ -20,8 +20,6 @@ import de.learnlib.datastructure.discriminationtree.SplitData; import de.learnlib.datastructure.list.IntrusiveList; import de.learnlib.datastructure.list.IntrusiveListElem; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * An extension of the {@link AbstractDTNode} that adds the concept of temporary splitters as well as linking @@ -46,8 +44,8 @@ public abstract class AbstractTemporaryIntrusiveDTNode splitData; - protected @Nullable IntrusiveListElem prevElement; - protected @Nullable N nextElement; + protected IntrusiveListElem prevElement; + protected N nextElement; // LEAF NODE DATA private boolean temp; @@ -56,7 +54,6 @@ public AbstractTemporaryIntrusiveDTNode(N parent, O parentOutcome, D data) { super(parent, parentOutcome, data); } - @RequiresNonNull("children") public void setChild(O label, N newChild) { assert newChild.parent == this; assert Objects.equals(newChild.getParentOutcome(), label); @@ -80,7 +77,6 @@ public void setSplitData(SplitData splitData) { this.splitData = splitData; } - @RequiresNonNull("children") public N anyChild() { assert isInner(); return children.values().iterator().next(); @@ -106,20 +102,20 @@ public void removeFromBlockList() { } @Override - public @Nullable N getNextElement() { + public N getNextElement() { return nextElement; } @Override - public void setNextElement(@Nullable N nextBlock) { + public void setNextElement(N nextBlock) { this.nextElement = nextBlock; } - public @Nullable IntrusiveListElem getPrevElement() { + public IntrusiveListElem getPrevElement() { return prevElement; } - public void setPrevElement(@Nullable IntrusiveListElem prevElement) { + public void setPrevElement(IntrusiveListElem prevElement) { this.prevElement = prevElement; } } diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractWordBasedDTNode.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractWordBasedDTNode.java index d524ed235a..af37a08b53 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractWordBasedDTNode.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractWordBasedDTNode.java @@ -16,7 +16,6 @@ package de.learnlib.datastructure.discriminationtree.model; import net.automatalib.words.Word; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Convenient class for word-based discrimination tree nodes that already binds certain generics. @@ -37,7 +36,7 @@ public AbstractWordBasedDTNode(D data) { super(data); } - public AbstractWordBasedDTNode(AbstractWordBasedDTNode parent, O parentOutcome, @Nullable D data) { + public AbstractWordBasedDTNode(AbstractWordBasedDTNode parent, O parentOutcome, D data) { super(parent, parentOutcome, data); } } diff --git a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/BooleanMap.java b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/BooleanMap.java index 2b33275154..46f08e31ff 100644 --- a/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/BooleanMap.java +++ b/datastructures/discrimination-tree/src/main/java/de/learnlib/datastructure/discriminationtree/model/BooleanMap.java @@ -27,8 +27,6 @@ import java.util.Objects; import java.util.Set; -import org.checkerframework.checker.nullness.qual.Nullable; - /** * Primitive implementation for boolean maps. * @@ -70,7 +68,7 @@ public boolean containsKey(Object key) { } @Override - public @Nullable V get(Object key) { + public V get(Object key) { if (key == null || key.getClass() != Boolean.class) { return null; } @@ -107,7 +105,7 @@ public V put(boolean key, V value) { } @Override - public @Nullable V remove(Object key) { + public V remove(Object key) { if (key == null || key.getClass() != Boolean.class) { return null; } @@ -211,12 +209,11 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof de.learnlib.datastructure.discriminationtree.model.BooleanMap.Entry)) { + if (!(o instanceof BooleanMap.Entry)) { return false; } - final de.learnlib.datastructure.discriminationtree.model.BooleanMap.Entry that = - (de.learnlib.datastructure.discriminationtree.model.BooleanMap.Entry) o; + final BooleanMap.Entry that = (BooleanMap.Entry) o; return Objects.equals(key, that.key); } } diff --git a/datastructures/list/pom.xml b/datastructures/list/pom.xml index 0e7a3118a6..1e5b574d8b 100644 --- a/datastructures/list/pom.xml +++ b/datastructures/list/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-datastructures-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveList.java b/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveList.java index 1c4cbf5487..acc7e57bc3 100644 --- a/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveList.java +++ b/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveList.java @@ -18,6 +18,8 @@ import java.util.Iterator; import com.google.common.collect.AbstractIterator; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; +import org.checkerframework.checker.nullness.qual.Nullable; /** * The head of the intrusive linked list for storing incoming transitions of a DT node. @@ -29,6 +31,7 @@ */ public class IntrusiveList> extends IntrusiveListElemImpl implements Iterable { + @EnsuresNonNullIf(expression = "next", result = false) public boolean isEmpty() { return next == null; } @@ -38,7 +41,7 @@ public boolean isEmpty() { * * @return any block from the list, or {@code null} if the list is empty. */ - public T choose() { + public @Nullable T choose() { return next; } @@ -60,9 +63,9 @@ public Iterator iterator() { private class ListIterator extends AbstractIterator { - private T cursor; + private @Nullable T cursor; - ListIterator(T start) { + ListIterator(@Nullable T start) { this.cursor = start; } diff --git a/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveListElemImpl.java b/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveListElemImpl.java index 8512a0da0c..27f0c8f267 100644 --- a/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveListElemImpl.java +++ b/datastructures/list/src/main/java/de/learnlib/datastructure/list/IntrusiveListElemImpl.java @@ -17,6 +17,8 @@ import java.io.Serializable; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * An element in an {@link IntrusiveList}. * @@ -27,15 +29,15 @@ */ public class IntrusiveListElemImpl implements IntrusiveListElem, Serializable { - protected T next; + protected @Nullable T next; @Override - public T getNextElement() { + public @Nullable T getNextElement() { return next; } @Override - public void setNextElement(T next) { + public void setNextElement(@Nullable T next) { this.next = next; } } diff --git a/datastructures/observation-table/pom.xml b/datastructures/observation-table/pom.xml index 948e60a424..5fb1fcb26f 100644 --- a/datastructures/observation-table/pom.xml +++ b/datastructures/observation-table/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-datastructures-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/GenericObservationTable.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/GenericObservationTable.java index 54e9026669..994cbdd1ae 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/GenericObservationTable.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/GenericObservationTable.java @@ -31,6 +31,7 @@ import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import net.automatalib.words.impl.Alphabets; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Observation table class. @@ -63,13 +64,13 @@ */ public final class GenericObservationTable implements MutableObservationTable, Serializable { - private static final Integer NO_ENTRY = null; // TODO: replace with primitive specialization + private static final int NO_ENTRY = -1; private final List> shortPrefixRows = new ArrayList<>(); // private static final int NO_ENTRY = -1; private final List> longPrefixRows = new ArrayList<>(); private final List> allRows = new ArrayList<>(); private final List> allRowContents = new ArrayList<>(); - private final List> canonicalRows = new ArrayList<>(); + private final List<@Nullable RowImpl> canonicalRows = new ArrayList<>(); // private final TObjectIntMap> rowContentIds = new TObjectIntHashMap<>(10, 0.75f, NO_ENTRY); private final Map, Integer> rowContentIds = new HashMap<>(); // TODO: replace with primitive specialization private final Map, RowImpl> rowMap = new HashMap<>(); @@ -237,10 +238,9 @@ private static void fetchResults(Iterator> queryIt, Li } private boolean processContents(RowImpl row, List rowContents, boolean makeCanonical) { - Integer contentId; // TODO: replace with primitive specialization - // int contentId; + int contentId; boolean added = false; - contentId = rowContentIds.get(rowContents); + contentId = rowContentIds.getOrDefault(rowContents, NO_ENTRY); if (contentId == NO_ENTRY) { contentId = numberOfDistinctRows(); rowContentIds.put(rowContents, contentId); @@ -511,10 +511,12 @@ public void setInputAlphabet(Alphabet alphabet) { @Override public Word transformAccessSequence(Word word) { Row current = shortPrefixRows.get(0); + assert current != null; for (I sym : word) { current = getRowSuccessor(current, sym); current = canonicalRows.get(current.getRowContentId()); + assert current != null; } return current.getLabel(); diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/Inconsistency.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/Inconsistency.java index 9f3f635cef..7ba253b2f7 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/Inconsistency.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/Inconsistency.java @@ -15,8 +15,6 @@ */ package de.learnlib.datastructure.observationtable; -import org.checkerframework.checker.nullness.qual.Nullable; - /** * A description of an inconsistency in an {@link GenericObservationTable}. An inconsistency consists of two short * prefixes u, u' with identical contents, and an input symbol a, such that the @@ -62,7 +60,7 @@ public Row getSecondRow() { * * @return the symbol */ - public @Nullable I getSymbol() { + public I getSymbol() { return symbol; } } diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/OTUtils.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/OTUtils.java index f2ebe8cc5d..860450ed44 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/OTUtils.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/OTUtils.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.io.Writer; +import java.util.Objects; import java.util.function.Function; import de.learnlib.datastructure.observationtable.reader.ObservationTableReader; @@ -65,7 +66,7 @@ public static ObservationTable fromString(String source, } public static void writeHTMLToFile(ObservationTable table, File file) throws IOException { - writeHTMLToFile(table, file, Object::toString, Object::toString); + writeHTMLToFile(table, file, Objects::toString, Objects::toString); } public static void writeHTMLToFile(ObservationTable table, @@ -86,7 +87,7 @@ public static void writeHTMLToFile(ObservationTable table, * Object#toString()} to render words and outputs of the observation table. */ public static void displayHTMLInBrowser(ObservationTable table) throws IOException { - displayHTMLInBrowser(table, Object::toString, Object::toString); + displayHTMLInBrowser(table, Objects::toString, Objects::toString); } /** diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/ObservationTable.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/ObservationTable.java index 3e555d35ab..2b203609fd 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/ObservationTable.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/ObservationTable.java @@ -117,7 +117,18 @@ default Collection> getLongPrefixes() { Collection> getLongPrefixRows(); - @Nullable Row getRow(int idx); + /** + * Returns the specified row of the observation table. + * + * @param idx + * the index of the row + * + * @return the row + * + * @throws IndexOutOfBoundsException + * if {@code idx} is less than 0 or greater than {@code number of rows - 1}. + */ + Row getRow(int idx); default @Nullable Row getRow(Word prefix) { for (Row row : getAllRows()) { @@ -279,7 +290,6 @@ default boolean isConsistent() { return (findInconsistency() == null); } - default @Nullable Inconsistency findInconsistency() { @SuppressWarnings("unchecked") final Row[] canonicalRows = (Row[]) new Row[numberOfDistinctRows()]; diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SimpleObservationTable.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SimpleObservationTable.java index 7f5bcbda70..d972eafd88 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SimpleObservationTable.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SimpleObservationTable.java @@ -37,7 +37,7 @@ * @param * The output domain type. */ -public class SimpleObservationTable implements ObservationTable { +public class SimpleObservationTable implements ObservationTable { final List> suffixes; @@ -57,8 +57,8 @@ public Collection> getLongPrefixRows() { @Override - public @Nullable Row getRow(int idx) { - return null; + public Row getRow(int idx) { + throw new IndexOutOfBoundsException("This OT contains no data"); } @Override diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SuffixASCIIReader.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SuffixASCIIReader.java index 2e974946a0..f0b58ae3bc 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SuffixASCIIReader.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/reader/SuffixASCIIReader.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import com.google.common.collect.Maps; import de.learnlib.datastructure.observationtable.ObservationTable; @@ -54,7 +55,7 @@ private Map generateNameToSymbolMap(Alphabet alphabet) { Map nameToSymbol = Maps.newHashMapWithExpectedSize(alphabet.size()); for (I symbol : alphabet) { - String symbolName = symbol.toString(); + String symbolName = Objects.toString(symbol); if (nameToSymbol.containsKey(symbolName)) { throw new IllegalArgumentException( "Symbol name '" + symbolName + "' is used more than once in alphabet"); diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/AbstractObservationTableWriter.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/AbstractObservationTableWriter.java index 361bc73d5a..42dfd7c15a 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/AbstractObservationTableWriter.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/AbstractObservationTableWriter.java @@ -19,6 +19,7 @@ import java.util.function.Function; import net.automatalib.words.Word; +import org.checkerframework.checker.initialization.qual.UnknownInitialization; public abstract class AbstractObservationTableWriter implements ObservationTableWriter { @@ -42,7 +43,8 @@ public AbstractObservationTableWriter(Function, ? exte return Objects::toString; } - public void setWordToString(Function, ? extends String> wordToString) { + public void setWordToString(@UnknownInitialization(AbstractObservationTableWriter.class) AbstractObservationTableWriter this, + Function, ? extends String> wordToString) { this.wordToString = safeToStringFunction(wordToString); } diff --git a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/SuffixASCIIWriter.java b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/SuffixASCIIWriter.java index e1907406a8..846c1c4278 100644 --- a/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/SuffixASCIIWriter.java +++ b/datastructures/observation-table/src/main/java/de/learnlib/datastructure/observationtable/writer/SuffixASCIIWriter.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; import java.util.function.Function; import de.learnlib.datastructure.observationtable.ObservationTable; @@ -38,51 +40,34 @@ public class SuffixASCIIWriter extends AbstractObservationTableWriter, String> wordToString = new Function, String>() { - - @Nullable - @Override - public String apply(@Nullable Word is) { - if (is == null) { - return ""; - } - - boolean first = true; - - StringBuilder sb = new StringBuilder(); - - for (I symbol : is) { - if (first) { - first = false; - } else { - sb.append(SYMBOL_DELIMITER); - } + private static final Function, String> WORD_TO_STRING = is -> { + if (is == null || is.isEmpty()) { + return ""; + } - String stringRepresentation = symbol.toString(); + final StringJoiner joiner = new StringJoiner(SYMBOL_DELIMITER); - if (stringRepresentation.contains(SYMBOL_DELIMITER) || - stringRepresentation.contains(WORD_DELIMITER)) { - throw new IllegalArgumentException( - "Symbol '" + stringRepresentation + "' must not contain " + "delimiters '" + - SYMBOL_DELIMITER + "' or '" + WORD_DELIMITER + '\''); - } + for (@Nullable Object symbol : is) { + String stringRepresentation = Objects.toString(symbol); + if (stringRepresentation.contains(SYMBOL_DELIMITER) || stringRepresentation.contains(WORD_DELIMITER)) { + throw new IllegalArgumentException( + "Symbol '" + stringRepresentation + "' must not contain " + "delimiters '" + SYMBOL_DELIMITER + + "' or '" + WORD_DELIMITER + '\''); + } - sb.append(symbol.toString()); - } + joiner.add(stringRepresentation); + } - return sb.toString(); - } - }; + return joiner.toString(); + }; - super.setWordToString(wordToString); + public SuffixASCIIWriter() { + super(); + super.setWordToString(WORD_TO_STRING); } @Override - public void write(ObservationTable table, Appendable out) - throws IOException { + public void write(ObservationTable table, Appendable out) throws IOException { List> suffixes = table.getSuffixes(); StringBuilder sb = new StringBuilder(); diff --git a/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/MockedObservationTable.java b/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/MockedObservationTable.java index 3b51b8c458..b1d2541a31 100644 --- a/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/MockedObservationTable.java +++ b/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/MockedObservationTable.java @@ -25,7 +25,6 @@ import com.google.common.base.Preconditions; import de.learnlib.datastructure.observationtable.reader.SimpleObservationTable; import net.automatalib.words.Word; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Mock-up observation table for testing writers. @@ -91,8 +90,7 @@ public Collection> getLongPrefixRows() { } @Override - public @Nullable RowImpl getRow(int idx) { - Preconditions.checkPositionIndex(0, rows.size()); + public RowImpl getRow(int idx) { return rows.get(idx); } diff --git a/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/writer/ObservationTableWriterTest.java b/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/writer/ObservationTableWriterTest.java index 7edef9529e..50dbfb6dcf 100644 --- a/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/writer/ObservationTableWriterTest.java +++ b/datastructures/observation-table/src/test/java/de/learnlib/datastructure/observationtable/writer/ObservationTableWriterTest.java @@ -17,8 +17,8 @@ import java.io.IOException; import java.io.StringWriter; -import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -59,8 +59,10 @@ private static void testInternal(AbstractObservationTableWriter final StringWriter writerResult = new StringWriter(); writer.write(ot, writerResult); - final URI testResource = ObservationTableWriterTest.class.getResource(urlOfExpectedResult).toURI(); - final String expectedResult = new String(Files.readAllBytes(Paths.get(testResource)), StandardCharsets.UTF_8); + final URL resource = ObservationTableWriterTest.class.getResource(urlOfExpectedResult); + Assert.assertNotNull(resource); + final String expectedResult = + new String(Files.readAllBytes(Paths.get(resource.toURI())), StandardCharsets.UTF_8); Assert.assertEquals(writerResult.toString(), expectedResult); } diff --git a/datastructures/pom.xml b/datastructures/pom.xml index ce2ee896d0..1a21f2fde5 100644 --- a/datastructures/pom.xml +++ b/datastructures/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/datastructures/pta/pom.xml b/datastructures/pta/pom.xml index e36ee37e22..87daae7ace 100644 --- a/datastructures/pta/pom.xml +++ b/datastructures/pta/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-datastructures-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBasePTAState.java b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBasePTAState.java index f134348e53..3a2be5983d 100644 --- a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBasePTAState.java +++ b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBasePTAState.java @@ -20,19 +20,21 @@ import java.util.stream.Stream; import net.automatalib.commons.smartcollections.ArrayStorage; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public abstract class AbstractBasePTAState> implements Cloneable { protected SP property; - protected ArrayStorage transProperties; - protected ArrayStorage successors; + protected @MonotonicNonNull ArrayStorage transProperties; + protected @MonotonicNonNull ArrayStorage successors; protected int id = -1; public SP getStateProperty() { return property; } - public TP getTransProperty(int index) { + public @Nullable TP getTransProperty(int index) { if (transProperties == null) { return null; } @@ -43,7 +45,7 @@ public S copy() { return copy((transProperties != null) ? transProperties.clone() : null); } - public S copy(ArrayStorage newTPs) { + public S copy(@Nullable ArrayStorage newTPs) { try { @SuppressWarnings("unchecked") S copy = (S) clone(); @@ -57,7 +59,7 @@ public S copy(ArrayStorage newTPs) { } } - public S getSuccessor(int index) { + public @Nullable S getSuccessor(int index) { if (successors == null) { return null; } diff --git a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBlueFringePTA.java b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBlueFringePTA.java index 7e8691d5d2..ba4d22c038 100644 --- a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBlueFringePTA.java +++ b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/AbstractBlueFringePTA.java @@ -23,6 +23,7 @@ import java.util.stream.Stream; import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.Nullable; public abstract class AbstractBlueFringePTA> extends BasePTA { @@ -37,8 +38,7 @@ public S getRedState(@NonNegative int id) { return redStates.get(id); } - @NonNegative - public int getNumRedStates() { + public @NonNegative int getNumRedStates() { return redStates.size(); } @@ -68,7 +68,7 @@ private void makeRed(S qb) { redStates.add(qb); } - public RedBlueMerge tryMerge(S qr, S qb) { + public @Nullable RedBlueMerge tryMerge(S qr, S qb) { RedBlueMerge merge = new RedBlueMerge<>(this, qr, qb); if (!merge.merge()) { return null; diff --git a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/BasePTA.java b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/BasePTA.java index d86f53a0dd..f6e7587b8c 100644 --- a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/BasePTA.java +++ b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/BasePTA.java @@ -36,7 +36,6 @@ import net.automatalib.automata.MutableDeterministic; import net.automatalib.automata.UniversalDeterministicAutomaton; import net.automatalib.commons.util.Pair; -import net.automatalib.commons.util.functions.FunctionsUtil; import net.automatalib.graphs.Graph; import net.automatalib.util.automata.Automata; import net.automatalib.visualization.DefaultVisualizationHelper; @@ -60,7 +59,6 @@ public class BasePTA> implements UniversalDeterministicAutomaton, SP, TP> { - protected final @NonNegative int alphabetSize; protected final S root; @@ -200,14 +198,10 @@ public void toAutomaton(MutableDeterministic spExtractor, Function tpExtractor) { - final Function safeSPExtractor = FunctionsUtil.safeDefault(spExtractor); - final Function safeTPExtractor = FunctionsUtil.safeDefault(tpExtractor); - Map resultStates = new HashMap<>(); - Queue> queue = new ArrayDeque<>(); - SP2 initProp = safeSPExtractor.apply(root.getStateProperty()); + SP2 initProp = spExtractor.apply(root.getStateProperty()); S2 resultInit = automaton.addInitialState(initProp); queue.add(Pair.of(root, resultInit)); @@ -221,13 +215,13 @@ public void toAutomaton(MutableDeterministic> getVisualizationHelper() { @Override public boolean getNodeProperties(S node, Map properties) { - properties.put(NodeAttrs.LABEL, - node.getProperty() == null ? "" : node.getProperty().toString()); + final SP property = node.getProperty(); + properties.put(NodeAttrs.LABEL, property == null ? "" : property.toString()); return super.getNodeProperties(node, properties); } @@ -352,7 +346,7 @@ public S getSuccessor(PTATransition transition) { } @Override - public S getSuccessor(S state, Integer input) { + public @Nullable S getSuccessor(S state, Integer input) { return state.getSuccessor(input); } diff --git a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/RedBlueMerge.java b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/RedBlueMerge.java index baee2983d0..832a56ae22 100644 --- a/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/RedBlueMerge.java +++ b/datastructures/pta/src/main/java/de/learnlib/datastructure/pta/pta/RedBlueMerge.java @@ -29,6 +29,7 @@ import net.automatalib.automata.UniversalDeterministicAutomaton; import net.automatalib.commons.smartcollections.ArrayStorage; import net.automatalib.commons.util.Pair; +import org.checkerframework.checker.nullness.qual.Nullable; public class RedBlueMerge> { @@ -152,12 +153,14 @@ public boolean merge() { return true; } - private S cloneTopSucc(S succ, int i, Deque> stack, ArrayStorage newTPs) { + private S cloneTopSucc(S succ, int i, Deque> stack, @Nullable ArrayStorage newTPs) { S succClone = (newTPs != null) ? succ.copy(newTPs) : succ.copy(); if (succClone == succ) { return succ; } - S top = stack.peek().q; + FoldRecord peek = stack.peek(); + assert peek != null; + S top = peek.q; if (top.isRed()) { updateRedTransition(top, i, succClone); } else { @@ -187,6 +190,7 @@ private S cloneTop(S topState, Deque> stack) { while (!currSrc.isRed()) { S currSrcClone = currSrc.copy(); + assert currSrcClone.successors != null; currSrcClone.successors.set(currRec.i, currTgt); if (currSrcClone == currSrc) { return topClone; // we're done @@ -205,7 +209,7 @@ private S cloneTop(S topState, Deque> stack) { return topClone; } - private ArrayStorage getTransProperties(S q) { + private @Nullable ArrayStorage getTransProperties(S q) { if (q.isRed()) { int qId = q.id; ArrayStorage props = transPropMod.get(qId); @@ -227,7 +231,7 @@ private SP getStateProperty(S q) { return q.property; } - private S getSucc(S q, int i) { + private @Nullable S getSucc(S q, int i) { if (q.isRed()) { int qId = q.id; ArrayStorage modSuccs = succMod.get(qId); @@ -242,7 +246,7 @@ private void updateRedTransition(S redSrc, int input, S tgt) { updateRedTransition(redSrc, input, tgt, null); } - private void updateRedTransition(S redSrc, int input, S tgt, TP transProp) { + private void updateRedTransition(S redSrc, int input, S tgt, @Nullable TP transProp) { assert redSrc.isRed(); int id = redSrc.id; @@ -316,7 +320,7 @@ private boolean mergeRedStateProperty(S qr, S qb) { * can be merged, a new {@link ArrayStorage} containing the result of the merge is returned.

  • otherwise * (i.e., if no merge is possible), {@code null} is returned. */ - private ArrayStorage mergeTransProperties(ArrayStorage tps1, ArrayStorage tps2) { + private @Nullable ArrayStorage mergeTransProperties(ArrayStorage tps1, ArrayStorage tps2) { int len = tps1.size(); int i; @@ -431,7 +435,7 @@ private void incorporate(S state) { private Set states; @Override - public S getSuccessor(Pair transition) { + public @Nullable S getSuccessor(Pair transition) { final S source = transition.getFirst(); final Integer input = transition.getSecond(); @@ -460,6 +464,7 @@ public TP getTransitionProperty(Pair transition) { return transPropMod.get(source.id).get(input); } + assert source.transProperties != null; return source.transProperties.get(input); } @@ -478,7 +483,9 @@ public Collection getStates() { states = Sets.newHashSetWithExpectedSize(pta.size()); final Queue discoverQueue = new ArrayDeque<>(); - discoverQueue.add(getInitialState()); + S initialState = getInitialState(); + assert initialState != null; + discoverQueue.add(initialState); S iter; diff --git a/datastructures/pta/src/test/java/de/learnlib/datastructure/pta/MergedAutomatonTest.java b/datastructures/pta/src/test/java/de/learnlib/datastructure/pta/MergedAutomatonTest.java index 12f08f029c..3e2c7c7243 100644 --- a/datastructures/pta/src/test/java/de/learnlib/datastructure/pta/MergedAutomatonTest.java +++ b/datastructures/pta/src/test/java/de/learnlib/datastructure/pta/MergedAutomatonTest.java @@ -21,6 +21,7 @@ import de.learnlib.datastructure.pta.pta.BlueFringePTA; import de.learnlib.datastructure.pta.pta.BlueFringePTAState; +import de.learnlib.datastructure.pta.pta.RedBlueMerge; import net.automatalib.automata.UniversalDeterministicAutomaton; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; @@ -79,8 +80,11 @@ public void testMerge() { pta.promote(q2, (q) -> {}); pta.promote(q3, (q) -> {}); + final RedBlueMerge> merge = pta.tryMerge(q3, q4); + Assert.assertNotNull(merge); + final UniversalDeterministicAutomaton, Integer, ?, Boolean, Void> - mergedAutomaton = pta.tryMerge(q3, q4).toMergedAutomaton(); + mergedAutomaton = merge.toMergedAutomaton(); // subtree of 3 states has been subsumed Assert.assertEquals(pta.size() - 3, mergedAutomaton.size()); diff --git a/distribution/pom.xml b/distribution/pom.xml index 85cc4588ee..16b96b939c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/drivers/basic/pom.xml b/drivers/basic/pom.xml index 2d87bec95f..061069a7f7 100644 --- a/drivers/basic/pom.xml +++ b/drivers/basic/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-drivers-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ConcreteMethodInput.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ConcreteMethodInput.java index 9a96f75b03..0fa557b8ee 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ConcreteMethodInput.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ConcreteMethodInput.java @@ -21,13 +21,14 @@ import de.learnlib.api.exception.SULException; import de.learnlib.mapper.api.ExecutableInput; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A concrete inputs contains the information for one specific method call. * * @author falkhowar */ -public class ConcreteMethodInput implements ExecutableInput { +public class ConcreteMethodInput implements ExecutableInput<@Nullable Object> { /** * corresponding abstract input. @@ -59,8 +60,10 @@ private Object[] getParameterValues() { return this.input.getParameters(values); } + // RuntimeExceptions are the type of exceptions we allow to handle, therefore we should throw them + @SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "PMD.PreserveStackTrace"}) @Override - public Object execute() { + public @Nullable Object execute() { Object out; try { Object ret = this.input.getMethod().invoke(this.target, getParameterValues()); diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/Error.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/Error.java index 56fd0ff914..14853a6727 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/Error.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/Error.java @@ -17,6 +17,8 @@ import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Error output. * @@ -39,7 +41,7 @@ public final int hashCode() { } @Override - public final boolean equals(Object obj) { + public final boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/MethodInput.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/MethodInput.java index ddcc790f40..43292e6a0f 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/MethodInput.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/MethodInput.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Map.Entry; +import org.checkerframework.checker.nullness.qual.KeyFor; + /** * abstract method input, may have abstract parameters. * @@ -61,7 +63,7 @@ public String getCall() { return this.method.getName() + Arrays.toString(getParameters(names)); } - public Collection getParameterNames() { + public Collection<@KeyFor("parameters") String> getParameterNames() { return this.parameters.keySet(); } @@ -75,7 +77,7 @@ public Object[] getParameters(Map fill) { return ret; } - public Class getParameterType(String name) { + public Class getParameterType(@KeyFor("parameters") String name) { int id = parameters.get(name); return this.method.getParameterTypes()[id]; } diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ReturnValue.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ReturnValue.java index 950067a804..01476fe701 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ReturnValue.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/ReturnValue.java @@ -17,6 +17,8 @@ import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * Error output. * @@ -24,11 +26,11 @@ */ public class ReturnValue extends MethodOutput { - private final Object ret; + private final @Nullable Object ret; private final String id; - public ReturnValue(Object ret) { + public ReturnValue(@Nullable Object ret) { this.ret = ret; this.id = String.valueOf(ret); } @@ -39,7 +41,7 @@ public final int hashCode() { } @Override - public final boolean equals(Object obj) { + public final boolean equals(@Nullable Object obj) { if (this == obj) { return true; } @@ -59,7 +61,7 @@ public String toString() { /** * @return the cause */ - public Object getValue() { + public @Nullable Object getValue() { return ret; } diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJODataMapper.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJODataMapper.java index d4520b3185..babd603a2e 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJODataMapper.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJODataMapper.java @@ -18,7 +18,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; -import java.util.Map; import de.learnlib.api.exception.SULException; import de.learnlib.mapper.api.SULMapper; @@ -29,7 +28,8 @@ * * @author falkhowar */ -public class SimplePOJODataMapper implements SULMapper { +public class SimplePOJODataMapper + implements SULMapper { private final Constructor initMethod; private final Object[] initParams; @@ -41,6 +41,8 @@ protected SimplePOJODataMapper(Constructor initMethod, Object... initParams) this.initParams = initParams; } + // RuntimeExceptions are the type of exceptions we allow to handle, therefore we should throw them + @SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "PMD.PreserveStackTrace"}) @Override public void pre() { try { @@ -59,18 +61,18 @@ public void post() { @Override public MappedException mapUnwrappedException(RuntimeException exception) { - return MappedException.repeatOutput(new Error(exception.getCause()), Unobserved.INSTANCE); + final Throwable cause = exception.getCause(); + return MappedException.repeatOutput(new Error(cause != null ? cause : exception), Unobserved.INSTANCE); } @Override public ConcreteMethodInput mapInput(MethodInput abstractInput) { - Map params = new HashMap<>(); - - return new ConcreteMethodInput(abstractInput, params, delegate); + assert delegate != null; + return new ConcreteMethodInput(abstractInput, new HashMap<>(), delegate); } @Override - public MethodOutput mapOutput(Object concreteOutput) { + public MethodOutput mapOutput(@Nullable Object concreteOutput) { return new ReturnValue(concreteOutput); } @@ -80,7 +82,7 @@ public boolean canFork() { } @Override - public SULMapper fork() { + public SULMapper fork() { return new SimplePOJODataMapper(initMethod, initParams); } diff --git a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJOTestDriver.java b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJOTestDriver.java index 3d8efe2f60..2b3d0686b3 100644 --- a/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJOTestDriver.java +++ b/drivers/basic/src/main/java/de/learnlib/drivers/reflect/SimplePOJOTestDriver.java @@ -24,6 +24,7 @@ import net.automatalib.words.Alphabet; import net.automatalib.words.GrowingAlphabet; import net.automatalib.words.impl.GrowingMapAlphabet; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Simple test driver for plain java objects. Uses a very simple data mapper without state or storage. Inputs cannot @@ -32,7 +33,7 @@ * @author falkhowar */ public final class SimplePOJOTestDriver - extends TestDriver { + extends TestDriver { private final GrowingAlphabet inputs = new GrowingMapAlphabet<>(); diff --git a/drivers/mapper/pom.xml b/drivers/mapper/pom.xml index 68febb92f1..c64eb95886 100644 --- a/drivers/mapper/pom.xml +++ b/drivers/mapper/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-drivers-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -47,6 +47,11 @@ limitations under the License. automata-core + + org.checkerframework + checker-qual + + org.testng diff --git a/drivers/mapper/src/main/java/de/learnlib/mapper/MappedSUL.java b/drivers/mapper/src/main/java/de/learnlib/mapper/MappedSUL.java index b62fca742f..07dcd99579 100644 --- a/drivers/mapper/src/main/java/de/learnlib/mapper/MappedSUL.java +++ b/drivers/mapper/src/main/java/de/learnlib/mapper/MappedSUL.java @@ -51,6 +51,7 @@ public void post() { mapper.post(); } + @SuppressWarnings("PMD.AvoidCatchingGenericException") // we want to allow mapping generic RuntimeExceptions @Override public AO step(AI in) { if (inError) { diff --git a/drivers/mapper/src/main/java/de/learnlib/mapper/SULMapperComposition.java b/drivers/mapper/src/main/java/de/learnlib/mapper/SULMapperComposition.java index 9a53c1613b..d3d299d91a 100644 --- a/drivers/mapper/src/main/java/de/learnlib/mapper/SULMapperComposition.java +++ b/drivers/mapper/src/main/java/de/learnlib/mapper/SULMapperComposition.java @@ -29,6 +29,7 @@ final class SULMapperComposition super(outerMapper, innerMapper); } + @SuppressWarnings("PMD.AvoidCatchingGenericException") // we want to allow mapping generic RuntimeExceptions @Override public MappedException mapWrappedException(SULException exception) { MappedException mappedEx; @@ -43,6 +44,7 @@ public MappedException mapWrappedException(SULException exception) return mapMappedException(mappedEx); } + @SuppressWarnings("PMD.AvoidCatchingGenericException") // we want to allow mapping generic RuntimeExceptions @Override public MappedException mapUnwrappedException(RuntimeException exception) { MappedException mappedEx; diff --git a/drivers/mapper/src/main/java/de/learnlib/mapper/api/SULMapper.java b/drivers/mapper/src/main/java/de/learnlib/mapper/api/SULMapper.java index 0bdc17f77a..c5073a52b5 100644 --- a/drivers/mapper/src/main/java/de/learnlib/mapper/api/SULMapper.java +++ b/drivers/mapper/src/main/java/de/learnlib/mapper/api/SULMapper.java @@ -22,6 +22,7 @@ import de.learnlib.api.SUL; import de.learnlib.api.exception.SULException; import de.learnlib.mapper.SULMappers; +import org.checkerframework.checker.nullness.qual.Nullable; /** * An extension of the {@link Mapper} interface specifically for {@link SUL}s. @@ -113,14 +114,13 @@ final class MappedException { private final AO thisStepOutput; private final Optional subsequentStepsOutput; - private MappedException(AO thisStepOutput, AO subsequentStepsOutput) { - this.thisStepOutput = thisStepOutput; - this.subsequentStepsOutput = Optional.of(subsequentStepsOutput); + private MappedException(AO output) { + this(output, null); } - private MappedException(AO output) { - this.thisStepOutput = output; - this.subsequentStepsOutput = Optional.empty(); + private MappedException(AO thisStepOutput, @Nullable AO subsequentStepsOutput) { + this.thisStepOutput = thisStepOutput; + this.subsequentStepsOutput = Optional.ofNullable(subsequentStepsOutput); } public static MappedException ignoreAndContinue(AO output) { diff --git a/drivers/pom.xml b/drivers/pom.xml index 9909113502..7b31ad114f 100644 --- a/drivers/pom.xml +++ b/drivers/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/drivers/simulator/pom.xml b/drivers/simulator/pom.xml index 68f25095b1..e27ab910fa 100644 --- a/drivers/simulator/pom.xml +++ b/drivers/simulator/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-drivers-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 120284bc70..1aaa4b7526 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml @@ -87,6 +87,10 @@ limitations under the License. de.learnlib learnlib-membership-oracles + + de.learnlib + learnlib-parallelism + de.learnlib learnlib-property-oracles @@ -149,6 +153,11 @@ limitations under the License. automata-serialization-dot + + org.checkerframework + checker-qual + + org.testng diff --git a/examples/src/main/java/de/learnlib/examples/Example1.java b/examples/src/main/java/de/learnlib/examples/Example1.java index 927cc87651..86ab33ae86 100644 --- a/examples/src/main/java/de/learnlib/examples/Example1.java +++ b/examples/src/main/java/de/learnlib/examples/Example1.java @@ -41,6 +41,7 @@ * * @author falkhowar */ +@SuppressWarnings("PMD.SystemPrintln") public final class Example1 { private static final int EXPLORATION_DEPTH = 4; diff --git a/examples/src/main/java/de/learnlib/examples/Example2.java b/examples/src/main/java/de/learnlib/examples/Example2.java index 1578457ddc..f2b6c68fd7 100644 --- a/examples/src/main/java/de/learnlib/examples/Example2.java +++ b/examples/src/main/java/de/learnlib/examples/Example2.java @@ -41,6 +41,7 @@ import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.visualization.Visualization; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; /** * This example shows how a model of a Java class can be learned using the SUL (system under learning) interfaces and @@ -48,6 +49,7 @@ * * @author falkhowar */ +@SuppressWarnings("PMD.SystemPrintln") public final class Example2 { private static final double RESET_PROBABILITY = 0.05; @@ -176,7 +178,7 @@ public void offer(String s) { } // get next element from queue (null for empty queue) - public String poll() { + public @Nullable String poll() { return data.poll(); } } diff --git a/examples/src/main/java/de/learnlib/examples/Example3.java b/examples/src/main/java/de/learnlib/examples/Example3.java index 974036a2c6..158acd670c 100644 --- a/examples/src/main/java/de/learnlib/examples/Example3.java +++ b/examples/src/main/java/de/learnlib/examples/Example3.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.function.Supplier; import de.learnlib.algorithms.lstar.mealy.ExtensibleLStarMealyBuilder; import de.learnlib.api.algorithm.LearningAlgorithm.MealyLearner; @@ -34,16 +35,17 @@ import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; import net.automatalib.words.impl.GrowingMapAlphabet; +import org.checkerframework.checker.nullness.qual.Nullable; /** - * This example shows how to use the reuse filter on the {@link BoundedStringQueue} of {@link - * Example2}. + * This example shows how to use the reuse filter on the {@link BoundedStringQueue} of {@link Example2}. *

    * Please note that there is no equivalence oracle used in this example so the resulting mealy machines are only first * "guesses". * * @author Oliver Bauer */ +@SuppressWarnings("PMD.SystemPrintln") public class Example3 { private static final String OFFER_1 = "offer_1"; @@ -68,10 +70,10 @@ public static void main(String[] args) { Example3 example = new Example3(); System.out.println("--"); System.out.println("Run experiment 1 (ReuseOracle):"); - MealyMachine result1 = example.runExperiment1(); + MealyMachine result1 = example.runExperiment1(); System.out.println("--"); System.out.println("Run experiment 2:"); - MealyMachine result2 = example.runExperiment2(); + MealyMachine result2 = example.runExperiment2(); System.out.println("--"); System.out.println("Model 1: " + result1.size() + " states"); System.out.println("Model 2: " + result2.size() + " states"); @@ -96,7 +98,7 @@ public static void main(String[] args) { /* * A "normal" scenario without reuse filter technique. */ - public MealyMachine runExperiment1() { + public MealyMachine runExperiment1() { // For each membership query a new instance of BoundedStringQueue will // be created (normal learning scenario without filters) FullMembershipQueryOracle oracle = new FullMembershipQueryOracle(); @@ -104,14 +106,14 @@ public static void main(String[] args) { // construct L* instance (almost classic Mealy version) // almost: we use words (Word) in cells of the table // instead of single outputs. - MealyLearner lstar; - lstar = new ExtensibleLStarMealyBuilder().withAlphabet(sigma) - .withInitialSuffixes(initialSuffixes) - .withOracle(oracle) - .create(); + MealyLearner lstar; + lstar = new ExtensibleLStarMealyBuilder().withAlphabet(sigma) + .withInitialSuffixes(initialSuffixes) + .withOracle(oracle) + .create(); lstar.startLearning(); - MealyMachine result; + MealyMachine result; result = lstar.getHypothesisModel(); System.out.println("Resets: " + oracle.resets); @@ -123,28 +125,29 @@ public static void main(String[] args) { /* * Scenario with reuse filter technique. */ - public MealyMachine runExperiment2() { + public MealyMachine runExperiment2() { MySystemStateHandler ssh = new MySystemStateHandler(); // This time we use the reuse filter to avoid some resets and // save execution of symbols - ReuseOracle reuseOracle = - new ReuseOracleBuilder<>(sigma, ReuseCapableImpl::new).withSystemStateHandler(ssh).build(); + Supplier> supplier = ReuseCapableImpl::new; + ReuseOracle reuseOracle = + new ReuseOracleBuilder<>(sigma, supplier).withSystemStateHandler(ssh).build(); // construct L* instance (almost classic Mealy version) // almost: we use words (Word) in cells of the table // instead of single outputs. - MealyLearner lstar; - lstar = new ExtensibleLStarMealyBuilder().withAlphabet(sigma) - .withInitialSuffixes(initialSuffixes) - .withOracle(reuseOracle) - .create(); + MealyLearner lstar; + lstar = new ExtensibleLStarMealyBuilder().withAlphabet(sigma) + .withInitialSuffixes(initialSuffixes) + .withOracle(reuseOracle) + .create(); lstar.startLearning(); // get learned model - MealyMachine result = lstar.getHypothesisModel(); + MealyMachine result = lstar.getHypothesisModel(); // now invalidate all system states and count the number of disposed // queues (equals number of resets) @@ -159,7 +162,7 @@ public static void main(String[] args) { return result; } - private String exec(BoundedStringQueue s, String input) { + private @Nullable String exec(BoundedStringQueue s, String input) { switch (input) { case OFFER_1: case OFFER_2: @@ -194,20 +197,20 @@ public void dispose(BoundedStringQueue state) { /** * An oracle that also does the reset by creating a new instance of the {@link BoundedStringQueue}. */ - class FullMembershipQueryOracle implements MealyMembershipOracle { + class FullMembershipQueryOracle implements MealyMembershipOracle { private int resets; private int symbols; @Override - public void processQueries(Collection>> queries) { - for (Query> query : queries) { + public void processQueries(Collection>> queries) { + for (Query> query : queries) { resets++; symbols += query.getInput().size(); BoundedStringQueue s = new BoundedStringQueue(); - WordBuilder output = new WordBuilder<>(); + WordBuilder<@Nullable String> output = new WordBuilder<>(); for (String input : query.getInput()) { output.add(exec(s, input)); } @@ -221,45 +224,46 @@ public void processQueries(Collection>> que * An implementation of the {@link ReuseCapableOracle} needed for the {@link ReuseOracle}. It only does resets (by * means of creating a new {@link BoundedStringQueue} instance in {@link ReuseCapableOracle#processQuery(Word)}. */ - class ReuseCapableImpl implements ReuseCapableOracle { + class ReuseCapableImpl implements ReuseCapableOracle { private int reused; private int fullQueries; private int symbols; @Override - public QueryResult continueQuery(Word trace, BoundedStringQueue s) { + public QueryResult continueQuery(Word trace, + BoundedStringQueue s) { reused++; symbols += trace.size(); - WordBuilder output = new WordBuilder<>(); + WordBuilder<@Nullable String> output = new WordBuilder<>(); for (String input : trace) { output.add(exec(s, input)); } - QueryResult result; + QueryResult result; result = new QueryResult<>(output.toWord(), s); return result; } @Override - public QueryResult processQuery(Word trace) { + public QueryResult processQuery(Word trace) { fullQueries++; symbols += trace.size(); // Suppose the reset would be a time consuming operation BoundedStringQueue s = new BoundedStringQueue(); - WordBuilder output = new WordBuilder<>(); + WordBuilder<@Nullable String> output = new WordBuilder<>(); for (String input : trace) { output.add(exec(s, input)); } - QueryResult result; + QueryResult result; result = new QueryResult<>(output.toWord(), s); return result; diff --git a/examples/src/main/java/de/learnlib/examples/parallelism/ParallelismExample1.java b/examples/src/main/java/de/learnlib/examples/parallelism/ParallelismExample1.java new file mode 100644 index 0000000000..cb0b9afdc6 --- /dev/null +++ b/examples/src/main/java/de/learnlib/examples/parallelism/ParallelismExample1.java @@ -0,0 +1,123 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.examples.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; + +import de.learnlib.api.SUL; +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.MembershipOracle.MealyMembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; +import de.learnlib.api.query.DefaultQuery; +import de.learnlib.driver.util.MealySimulatorSUL; +import de.learnlib.oracle.membership.SULOracle; +import de.learnlib.oracle.membership.SimulatorOracle.MealySimulatorOracle; +import de.learnlib.oracle.parallelism.ParallelOracleBuilders; +import net.automatalib.automata.transducers.impl.compact.CompactMealy; +import net.automatalib.commons.util.collections.CollectionsUtil; +import net.automatalib.util.automata.random.RandomAutomata; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; + +/** + * An example for creating {@link ParallelOracle}s using the {@link ParallelOracleBuilders} factory. + * + * @author frohme + */ +@SuppressWarnings("PMD.SystemPrintln") +public class ParallelismExample1 { + + private static final int AUTOMATON_SIZE = 100; + private static final int QUERIES_EXP = 6; + + private final CompactMealy automaton; + private final SUL automatonAsSUL; + + private final Collection>> queries; + + public ParallelismExample1() { + final Alphabet inputs = Alphabets.integers(0, 9); + final Alphabet outputs = Alphabets.characters('a', 'z'); + this.automaton = RandomAutomata.randomMealy(new Random(0), AUTOMATON_SIZE, inputs, outputs); + this.automatonAsSUL = new MealySimulatorSUL<>(automaton); + + System.out.println("Generating queries"); + + // generate 1 million (10^6) input words + this.queries = new ArrayList<>((int) Math.pow(inputs.size(), QUERIES_EXP)); + for (List input : CollectionsUtil.allTuples(inputs, QUERIES_EXP)) { + queries.add(new DefaultQuery<>(Word.fromList(input))); + } + } + + public static void main(String[] args) { + ParallelismExample1 example = new ParallelismExample1(); + + example.runSequentialOracles(); + example.runParallelOracles(); + } + + private void runSequentialOracles() { + final MealyMembershipOracle sequenceSULOracle = new SULOracle<>(automatonAsSUL); + final MealySimulatorOracle sequentialMQOracle = new MealySimulatorOracle<>(automaton); + + System.out.println("Sequential SUL oracle"); + answerQueries(sequenceSULOracle); + + System.out.println("Sequential MQ oracle"); + answerQueries(sequentialMQOracle); + } + + private void runParallelOracles() { + + // we want to limit the number of parallel threads to the number of available cores + final int availableProcessors = Runtime.getRuntime().availableProcessors(); + + final ParallelOracle> parallelSULOracle = + ParallelOracleBuilders.newStaticParallelOracle(this.automatonAsSUL) + .withNumInstances(availableProcessors) + .create(); + + final MealySimulatorOracle oracle = new MealySimulatorOracle<>(this.automaton); + // MealySimulatorOracle is thread-safe + final ParallelOracle> parallelMQOracle = + ParallelOracleBuilders.newStaticParallelOracle(() -> oracle) + .withNumInstances(availableProcessors) + .create(); + + System.out.println("Parallel SUL oracle"); + answerQueries(parallelSULOracle); + + System.out.println("Parallel MQ oracle"); + answerQueries(parallelMQOracle); + + parallelSULOracle.shutdownNow(); + parallelMQOracle.shutdownNow(); + } + + private void answerQueries(MembershipOracle> oracle) { + long t0 = System.currentTimeMillis(); + oracle.processQueries(queries); + long t1 = System.currentTimeMillis(); + + System.out.println(" Answering queries took " + (t1 - t0) + "ms"); + } + +} diff --git a/examples/src/main/java/de/learnlib/examples/sli/Example1.java b/examples/src/main/java/de/learnlib/examples/sli/Example1.java index 19f3d155c7..efa406840f 100644 --- a/examples/src/main/java/de/learnlib/examples/sli/Example1.java +++ b/examples/src/main/java/de/learnlib/examples/sli/Example1.java @@ -38,6 +38,7 @@ * * @author frohme */ +@SuppressWarnings("PMD.SystemPrintln") public final class Example1 { private static final Alphabet INPUTS; @@ -47,7 +48,7 @@ public final class Example1 { static { INPUTS = Alphabets.integers(0, 1); TARGET = constructTarget(); - UNDEFINED = null; + UNDEFINED = '-'; } private Example1() { diff --git a/examples/src/main/java/de/learnlib/examples/sli/Example2.java b/examples/src/main/java/de/learnlib/examples/sli/Example2.java index ff88b0bae8..50f398b2fe 100644 --- a/examples/src/main/java/de/learnlib/examples/sli/Example2.java +++ b/examples/src/main/java/de/learnlib/examples/sli/Example2.java @@ -51,6 +51,7 @@ * * @author frohme */ +@SuppressWarnings("PMD.SystemPrintln") public final class Example2 { private static final Alphabet INPUTS; diff --git a/examples/src/test/java/de/learnlib/examples/ExamplesTest.java b/examples/src/test/java/de/learnlib/examples/ExamplesTest.java index f837593a22..de66729a3d 100644 --- a/examples/src/test/java/de/learnlib/examples/ExamplesTest.java +++ b/examples/src/test/java/de/learnlib/examples/ExamplesTest.java @@ -81,6 +81,11 @@ public void testBBCExample4() { de.learnlib.examples.bbc.Example4.main(new String[0]); } + @Test + public void testParallelismExample1() { + de.learnlib.examples.parallelism.ParallelismExample1.main(new String[0]); + } + @Test public void testPassiveExample1() { checkJVMCompatibility(); diff --git a/oracles/emptiness-oracles/pom.xml b/oracles/emptiness-oracles/pom.xml index 80fa07f29e..02ca9ea450 100644 --- a/oracles/emptiness-oracles/pom.xml +++ b/oracles/emptiness-oracles/pom.xml @@ -5,7 +5,7 @@ de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/AbstractBFEmptinessOracle.java b/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/AbstractBFEmptinessOracle.java index 2388e1cf7d..0cbaa3d872 100644 --- a/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/AbstractBFEmptinessOracle.java +++ b/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/AbstractBFEmptinessOracle.java @@ -77,7 +77,7 @@ protected AbstractBFEmptinessOracle(MembershipOracle membershipOracle, dou } @Override - public boolean isCounterExample(A hypothesis, Iterable inputs, @Nullable D output) { + public boolean isCounterExample(A hypothesis, Iterable inputs, D output) { return EmptinessOracle.super.isCounterExample(hypothesis, inputs, output); } diff --git a/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/LassoEmptinessOracleImpl.java b/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/LassoEmptinessOracleImpl.java index 9cfd729fad..6a6507eb62 100644 --- a/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/LassoEmptinessOracleImpl.java +++ b/oracles/emptiness-oracles/src/main/java/de/learnlib/oracle/emptiness/LassoEmptinessOracleImpl.java @@ -86,7 +86,7 @@ public OmegaQuery processInput(Word prefix, Word loop, int repeat) { } @Override - public boolean isCounterExample(Output hypothesis, Iterable input, @Nullable D output) { + public boolean isCounterExample(Output hypothesis, Iterable input, D output) { return LassoEmptinessOracle.super.isCounterExample(hypothesis, input, output); } @@ -94,9 +94,4 @@ public boolean isCounterExample(Output hypothesis, Iterable i public @Nullable DefaultQuery findCounterExample(L hypothesis, Collection inputs) { return LassoOracle.super.findCounterExample(hypothesis, inputs); } - - @Override - public boolean isOmegaCounterExample(boolean isUltimatelyPeriodic) { - return LassoEmptinessOracle.super.isOmegaCounterExample(isUltimatelyPeriodic); - } } diff --git a/oracles/emptiness-oracles/src/test/java/de/learnlib/oracle/emptiness/AbstractLassoEmptinessOracleImplTest.java b/oracles/emptiness-oracles/src/test/java/de/learnlib/oracle/emptiness/AbstractLassoEmptinessOracleImplTest.java index 7fbd41e8d8..9ab8ac8a74 100644 --- a/oracles/emptiness-oracles/src/test/java/de/learnlib/oracle/emptiness/AbstractLassoEmptinessOracleImplTest.java +++ b/oracles/emptiness-oracles/src/test/java/de/learnlib/oracle/emptiness/AbstractLassoEmptinessOracleImplTest.java @@ -27,6 +27,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.Assert; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -42,6 +43,8 @@ public abstract class AbstractLassoEmptinessOracleImplTest ALPHABET = Alphabets.singleton('a'); + private AutoCloseable mock; + private LassoEmptinessOracleImpl leo; private final Word prefix = Word.epsilon(); @@ -64,13 +67,19 @@ public abstract class AbstractLassoEmptinessOracleImplTest { diff --git a/oracles/equivalence-oracles/pom.xml b/oracles/equivalence-oracles/pom.xml index 0b7385607f..4c857a7d1c 100644 --- a/oracles/equivalence-oracles/pom.xml +++ b/oracles/equivalence-oracles/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mealy/RandomWalkEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mealy/RandomWalkEQOracle.java index 5fab2458a0..6c57c93c97 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mealy/RandomWalkEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mealy/RandomWalkEQOracle.java @@ -140,6 +140,7 @@ public RandomWalkEQOracle(SUL sul, double restartProbability, long maxStep outSul = sul.step(in); + assert cur != null; O outHyp = hypothesis.getTransitionProperty(cur, in); wbIn.add(in); wbOut.add(outSul); diff --git a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/CExFirstOracleTest.java b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/CExFirstOracleTest.java index 45dccaf5cf..5645557d27 100644 --- a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/CExFirstOracleTest.java +++ b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/CExFirstOracleTest.java @@ -27,11 +27,14 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.Assert; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class CExFirstOracleTest { + private AutoCloseable mock; + @Mock() private PropertyOracle, Boolean, Boolean> po1; @@ -51,7 +54,7 @@ public class CExFirstOracleTest { @BeforeMethod public void setUp() { - MockitoAnnotations.initMocks(this); + mock = MockitoAnnotations.openMocks(this); // make sure the assertion check for InclusionOracle.isCounterExample passes Mockito.when(query.getInput()).thenReturn(null); @@ -65,6 +68,12 @@ public void setUp() { Mockito.when(po2.doFindCounterExample(automaton, inputs)).thenReturn(query); } + @AfterMethod + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + public void tearDown() throws Exception { + this.mock.close(); + } + @Test public void testGetPropertyOracles() { Assert.assertEquals(oracle.getPropertyOracles().size(), 2); diff --git a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/DisproveFirstOracleTest.java b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/DisproveFirstOracleTest.java index 2391ae172f..63465f0ab1 100644 --- a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/DisproveFirstOracleTest.java +++ b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/DisproveFirstOracleTest.java @@ -27,11 +27,14 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.testng.Assert; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class DisproveFirstOracleTest { + private AutoCloseable mock; + @Mock private PropertyOracle, Boolean, Boolean> po1; @@ -51,7 +54,7 @@ public class DisproveFirstOracleTest { @BeforeMethod public void setUp() { - MockitoAnnotations.initMocks(this); + mock = MockitoAnnotations.openMocks(this); // make sure the assertion check for InclusionOracle.isCounterExample passes Mockito.when(query.getInput()).thenReturn(null); @@ -65,6 +68,12 @@ public void setUp() { Mockito.when(po2.doFindCounterExample(automaton, inputs)).thenReturn(query); } + @AfterMethod + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + public void tearDown() throws Exception { + this.mock.close(); + } + @Test public void testGetPropertyOracles() { Assert.assertEquals(oracle.getPropertyOracles().size(), 2); diff --git a/oracles/filters/cache/pom.xml b/oracles/filters/cache/pom.xml index d1d550c0d6..f8de1b7dba 100644 --- a/oracles/filters/cache/pom.xml +++ b/oracles/filters/cache/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-filters-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/InternalMealyCacheOracle.java b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/InternalMealyCacheOracle.java index 690b1df0cd..b1903a1af3 100644 --- a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/InternalMealyCacheOracle.java +++ b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/InternalMealyCacheOracle.java @@ -34,6 +34,7 @@ import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; import net.automatalib.words.impl.GrowingMapAlphabet; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Mealy cache. This cache is implemented as a membership oracle: upon construction, it is provided with a delegate @@ -61,16 +62,16 @@ class InternalMealyCacheOracle implements MealyLearningCacheOracle, protected IncrementalMealyBuilder incMealy; protected final ReadWriteLock incMealyLock; private final Comparator> queryCmp; - private final Mapping errorSyms; + private final @Nullable Mapping errorSyms; InternalMealyCacheOracle(IncrementalMealyBuilder incrementalBuilder, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate) { this(incrementalBuilder, errorSyms, delegate, new DynamicSymbolComparator<>()); } InternalMealyCacheOracle(IncrementalMealyBuilder incrementalBuilder, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate, Comparator comparator) { this.incMealy = incrementalBuilder; diff --git a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MasterQuery.java b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MasterQuery.java index fed7d1e887..cd4bc3037a 100644 --- a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MasterQuery.java +++ b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MasterQuery.java @@ -24,7 +24,6 @@ import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * A "master" query. This query corresponds to a maximal input word in the batch, and all queries that constitute @@ -69,9 +68,9 @@ public boolean isAnswered() { return (answer != null); } - @RequiresNonNull("slaves") @Override public void answer(Word output) { + assert slaves != null; this.answer = truncateOutput(output); for (Query> slave : slaves) { answerSlave(slave); diff --git a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MealyCacheOracle.java b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MealyCacheOracle.java index 5178d209e8..bdc04b70e4 100644 --- a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MealyCacheOracle.java +++ b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/MealyCacheOracle.java @@ -28,6 +28,7 @@ import net.automatalib.incremental.mealy.tree.dynamic.DynamicIncrementalMealyTreeBuilder; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,13 +38,13 @@ public class MealyCacheOracle extends InternalMealyCacheOracle private static final Logger LOGGER = LoggerFactory.getLogger(MealyCacheOracle.class); MealyCacheOracle(IncrementalMealyBuilder incrementalBuilder, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate) { super(incrementalBuilder, errorSyms, delegate); } MealyCacheOracle(IncrementalMealyBuilder incrementalBuilder, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate, Comparator comparator) { super(incrementalBuilder, errorSyms, delegate, comparator); @@ -55,7 +56,7 @@ public static MealyCacheOracle createDAGCacheOracle(Alphabet inp } public static MealyCacheOracle createDAGCacheOracle(Alphabet inputAlphabet, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate) { IncrementalMealyBuilder incrementalBuilder = new IncrementalMealyDAGBuilder<>(inputAlphabet); return new MealyCacheOracle<>(incrementalBuilder, errorSyms, delegate, inputAlphabet); @@ -67,7 +68,7 @@ public static MealyCacheOracle createTreeCacheOracle(Alphabet in } public static MealyCacheOracle createTreeCacheOracle(Alphabet inputAlphabet, - Mapping errorSyms, + @Nullable Mapping errorSyms, MembershipOracle> delegate) { IncrementalMealyBuilder incrementalBuilder = new IncrementalMealyTreeBuilder<>(inputAlphabet); return new MealyCacheOracle<>(incrementalBuilder, errorSyms, delegate, inputAlphabet); @@ -77,7 +78,7 @@ public static MealyCacheOracle createDynamicTreeCacheOracle(Members return createDynamicTreeCacheOracle(null, delegate); } - public static MealyCacheOracle createDynamicTreeCacheOracle(Mapping errorSyms, + public static MealyCacheOracle createDynamicTreeCacheOracle(@Nullable Mapping errorSyms, MembershipOracle> delegate) { IncrementalMealyBuilder incrementalBuilder = new DynamicIncrementalMealyTreeBuilder<>(); return new MealyCacheOracle<>(incrementalBuilder, errorSyms, delegate); diff --git a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/SymbolQueryCache.java b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/SymbolQueryCache.java index dafb3a6548..48d8dd6e5f 100644 --- a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/SymbolQueryCache.java +++ b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/mealy/SymbolQueryCache.java @@ -75,6 +75,7 @@ public O query(I i) { if (succ != null) { final O output = this.cache.getOutput(this.currentState, i); + assert output != null; this.currentTrace.add(i); this.currentState = succ; return output; @@ -107,13 +108,15 @@ public O query(I i) { @Override public void reset() { - this.currentState = this.cache.getInitialState(); + Integer init = this.cache.getInitialState(); + assert init != null; + this.currentState = init; this.currentTrace.clear(); this.currentTraceValid = true; } @Override - public @Nullable EquivalenceOracle, I, Word> createCacheConsistencyTest() { + public EquivalenceOracle, I, Word> createCacheConsistencyTest() { return this::findCounterexample; } diff --git a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/sul/StateLocalInputSULCache.java b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/sul/StateLocalInputSULCache.java index e206b9834c..f34e363506 100644 --- a/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/sul/StateLocalInputSULCache.java +++ b/oracles/filters/cache/src/main/java/de/learnlib/filter/cache/sul/StateLocalInputSULCache.java @@ -97,8 +97,10 @@ private StateLocalInputSULCacheImpl(IncrementalMealyBuilder incMealy, StateLocalInputSUL sul) { super(incMealy, lock, mealyTs, sul); this.delegate = sul; + S init = mealyTs.getInitialState(); + assert init != null; + this.initialState = init; this.enabledInputCache = enabledInputCache; - this.initialState = mealyTs.getInitialState(); this.inputsTrace = new ArrayList<>(); } @@ -111,9 +113,11 @@ protected void postNewStepHook() { protected void postCacheWriteHook(List input) { final int prefixLength = input.size() - this.inputsTrace.size(); S iter = mealyTs.getSuccessor(initialState, input.subList(0, prefixLength)); + assert iter != null; for (int i = 0; i < this.inputsTrace.size(); i++) { iter = mealyTs.getSuccessor(iter, input.get(i + prefixLength)); + assert iter != null; this.enabledInputCache.put(iter, this.inputsTrace.get(i)); } @@ -158,7 +162,9 @@ public StateLocalInputSULCacheState suspend() { @SuppressWarnings("unchecked") public void resume(StateLocalInputSULCacheState state) { super.resume(state); - this.initialState = super.mealyTs.getInitialState(); + S init = super.mealyTs.getInitialState(); + assert init != null; + this.initialState = init; this.enabledInputCache = (Map>) state.enabledInputCache; } } diff --git a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/SULLearningCacheOracle.java b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/SULLearningCacheOracle.java index 2af61d97bc..b11a4b6f64 100644 --- a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/SULLearningCacheOracle.java +++ b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/SULLearningCacheOracle.java @@ -65,6 +65,6 @@ public static SULLearningCacheOracle> fromSULCache(S public static SULLearningCacheOracle> fromSLISULCache( StateLocalInputSULCache cache, O undefinedInput) { - return new SULLearningCacheOracle<>(cache, new StateLocalInputSULOracle<>(cache, undefinedInput)); + return new SULLearningCacheOracle<>(cache, new StateLocalInputSULOracle<>(cache.fork(), undefinedInput)); } } diff --git a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/AbstractParallelCacheTest.java b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/AbstractParallelCacheTest.java index 11ea8e3a3b..f45ddc8698 100644 --- a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/AbstractParallelCacheTest.java +++ b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/AbstractParallelCacheTest.java @@ -16,25 +16,17 @@ package de.learnlib.filter.cache.parallelism; import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.function.Consumer; import de.learnlib.api.oracle.EquivalenceOracle; -import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; import de.learnlib.api.query.DefaultQuery; -import de.learnlib.api.query.Query; -import de.learnlib.filter.cache.LearningCacheOracle; -import de.learnlib.filter.statistic.oracle.CounterOracle; -import de.learnlib.oracle.membership.SimulatorOracle; -import de.learnlib.oracle.parallelism.ParallelOracle; -import de.learnlib.oracle.parallelism.ParallelOracleBuilders; -import de.learnlib.util.MQUtil; -import net.automatalib.automata.concepts.SuffixOutput; +import de.learnlib.filter.cache.LearningCache; import net.automatalib.commons.util.collections.CollectionsUtil; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import org.testng.Assert; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -49,63 +41,45 @@ * * @author frohme */ -public abstract class AbstractParallelCacheTest, I, D> { +public abstract class AbstractParallelCacheTest, A, I, D> { protected static final int MODEL_SIZE = 10; - private static final int NUMBER_OF_THREADS = 10; - private static final int BATCH_SIZE = 10; private static final int MAXIMUM_LENGTH_OF_QUERIES = 5; - private CounterOracle counterOracle; private Alphabet alphabet; - private ParallelOracle parallelOracle; - private LearningCacheOracle cache; private A targetModel; + private S sul; + private C cache; + private ParallelOracle parallelOracle; protected abstract Alphabet getAlphabet(); - protected abstract A getTargetModel(); + protected abstract A getTargetModel(Alphabet alphabet); - protected abstract LearningCacheOracle getCache(Alphabet alphabet, MembershipOracle oracle); + protected abstract S getSUL(A targetModel); - @BeforeClass - public void setUp() { - this.alphabet = getAlphabet(); - this.targetModel = getTargetModel(); + protected abstract C getCache(Alphabet alphabet, S sul); - this.counterOracle = new CounterOracle<>(new SimulatorOracle<>(targetModel), "Queries"); - this.cache = getCache(this.alphabet, this.counterOracle); - } + protected abstract ParallelOracle getParallelOracle(C cache); - @Test(timeOut = 20000) - public void testConcurrentMembershipQueriesImplicit() { - testConcurrentMembershipQueries(queries -> MQUtil.answerQueriesParallel(cache, queries)); - } + protected abstract int getNumberOfQueries(S sul); - @Test(dependsOnMethods = "testConcurrentMembershipQueriesImplicit", timeOut = 20000) - public void testConcurrentEquivalenceQueriesImplicit() { - testConcurrentEquivalenceQueries(queries -> MQUtil.answerQueriesParallel(cache, queries)); - } - - @Test(dependsOnMethods = "testConcurrentEquivalenceQueriesImplicit", timeOut = 20000) - public void testConcurrentMembershipQueriesExplicit() { - setUp(); - this.parallelOracle = ParallelOracleBuilders.newDynamicParallelOracle(() -> cache) - .withPoolSize(NUMBER_OF_THREADS) - .withBatchSize(BATCH_SIZE) - .create(); - - testConcurrentMembershipQueries(parallelOracle::processQueries); + @BeforeClass + public void setUp() { + this.alphabet = getAlphabet(); + this.targetModel = getTargetModel(this.alphabet); + this.sul = getSUL(this.targetModel); + this.cache = getCache(this.alphabet, this.sul); + this.parallelOracle = getParallelOracle(this.cache); } - @Test(dependsOnMethods = "testConcurrentMembershipQueriesExplicit", timeOut = 20000) - public void testConcurrentEquivalenceQueriesExplicit() { - testConcurrentEquivalenceQueries(parallelOracle::processQueries); - parallelOracle.shutdownNow(); + @AfterClass + public void teardown() { + this.parallelOracle.shutdownNow(); } - private void testConcurrentMembershipQueries(Consumer>> mqConsumer) { - + @Test(timeOut = 20000) + public void testConcurrentMembershipQueries() { final List> queries = new ArrayList<>(((int) Math.pow(alphabet.size(), MAXIMUM_LENGTH_OF_QUERIES)) + 1); @@ -113,21 +87,21 @@ private void testConcurrentMembershipQueries(Consumer(Word.fromList(word))); } - mqConsumer.accept(queries); - final long numOfQueriesBefore = counterOracle.getCount(); + this.parallelOracle.processQueries(queries); + final long numOfQueriesBefore = getNumberOfQueries(this.sul); queries.forEach(q -> q.answer(null)); - mqConsumer.accept(queries); - final long numOfQueriesAfter = counterOracle.getCount(); + this.parallelOracle.processQueries(queries); + final long numOfQueriesAfter = getNumberOfQueries(this.sul); Assert.assertEquals(numOfQueriesAfter, numOfQueriesBefore); } - private void testConcurrentEquivalenceQueries(Consumer>> mqConsumer) { - - final long previousCount = counterOracle.getCount(); - final EquivalenceOracle eqOracle = cache.createCacheConsistencyTest(); + @Test(dependsOnMethods = "testConcurrentMembershipQueries", timeOut = 20000) + public void testConcurrentEquivalenceQueries() { + final long previousCount = getNumberOfQueries(this.sul); + final EquivalenceOracle eqOracle = cache.createCacheConsistencyTest(); final List> queries = new ArrayList<>( (int) Math.pow(alphabet.size(), MAXIMUM_LENGTH_OF_QUERIES + 1) - @@ -156,10 +130,10 @@ private void testConcurrentEquivalenceQueries(Consumer, Character, Boolean> { +public class DFAParallelCacheTest + extends AbstractParallelCacheTest, DFALearningCacheOracle, DFA, Character, Boolean> { @Override protected Alphabet getAlphabet() { @@ -36,13 +40,28 @@ protected Alphabet getAlphabet() { } @Override - protected DFA getTargetModel() { + protected DFA getTargetModel(Alphabet alphabet) { return RandomAutomata.randomDFA(new Random(42), MODEL_SIZE, getAlphabet()); } + @Override + protected DFACounterOracle getSUL(DFA targetModel) { + return new DFACounterOracle<>(new DFASimulatorOracle<>(targetModel), "Queries"); + } + @Override protected DFALearningCacheOracle getCache(Alphabet alphabet, - MembershipOracle oracle) { - return DFACaches.createCache(alphabet, oracle); + DFACounterOracle sul) { + return DFACaches.createCache(alphabet, sul); + } + + @Override + protected ParallelOracle getParallelOracle(DFALearningCacheOracle cache) { + return ParallelOracleBuilders.newDynamicParallelOracle(() -> cache).create(); + } + + @Override + protected int getNumberOfQueries(DFACounterOracle model) { + return (int) model.getCount(); } } diff --git a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/MealyParallelCacheTest.java b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/MealyParallelCacheTest.java index 62060cfc1c..4b8ee936a3 100644 --- a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/MealyParallelCacheTest.java +++ b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/MealyParallelCacheTest.java @@ -17,9 +17,12 @@ import java.util.Random; -import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; import de.learnlib.filter.cache.LearningCacheOracle.MealyLearningCacheOracle; import de.learnlib.filter.cache.mealy.MealyCaches; +import de.learnlib.filter.statistic.oracle.MealyCounterOracle; +import de.learnlib.oracle.membership.SimulatorOracle.MealySimulatorOracle; +import de.learnlib.oracle.parallelism.ParallelOracleBuilders; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.util.automata.random.RandomAutomata; import net.automatalib.words.Alphabet; @@ -30,7 +33,7 @@ * @author frohme */ public class MealyParallelCacheTest - extends AbstractParallelCacheTest, Character, Word> { + extends AbstractParallelCacheTest, MealyLearningCacheOracle, MealyMachine, Character, Word> { @Override protected Alphabet getAlphabet() { @@ -38,13 +41,28 @@ protected Alphabet getAlphabet() { } @Override - protected MealyMachine getTargetModel() { + protected MealyMachine getTargetModel(Alphabet alphabet) { return RandomAutomata.randomMealy(new Random(42), MODEL_SIZE, getAlphabet(), getAlphabet()); } + @Override + protected MealyCounterOracle getSUL(MealyMachine targetModel) { + return new MealyCounterOracle<>(new MealySimulatorOracle<>(targetModel), "Queries"); + } + @Override protected MealyLearningCacheOracle getCache(Alphabet alphabet, - MembershipOracle> oracle) { - return MealyCaches.createCache(alphabet, oracle); + MealyCounterOracle sul) { + return MealyCaches.createCache(alphabet, sul); + } + + @Override + protected ParallelOracle> getParallelOracle(MealyLearningCacheOracle cache) { + return ParallelOracleBuilders.newDynamicParallelOracle(() -> cache).create(); + } + + @Override + protected int getNumberOfQueries(MealyCounterOracle model) { + return (int) model.getStatisticalData().getCount(); } } diff --git a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULNoForkParallelCacheTest.java b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULNoForkParallelCacheTest.java deleted file mode 100644 index a60621c015..0000000000 --- a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULNoForkParallelCacheTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright (C) 2013-2020 TU Dortmund - * This file is part of LearnLib, http://www.learnlib.de/. - * - * 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 de.learnlib.filter.cache.parallelism; - -import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.filter.cache.LearningCacheOracle.MealyLearningCacheOracle; -import de.learnlib.filter.cache.SULLearningCacheOracle; -import de.learnlib.filter.cache.sul.SULCache; -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; - -/** - * @author frohme - */ -public class SULNoForkParallelCacheTest extends SULParallelCacheTest { - - @Override - protected MealyLearningCacheOracle getCache(Alphabet alphabet, - MembershipOracle> oracle) { - return SULLearningCacheOracle.fromSULCache(SULCache.createTreeCache(alphabet, - new TestSUL<>(getTargetModel(), - oracle, - false))); - } -} diff --git a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULParallelCacheTest.java b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULParallelCacheTest.java index 917bb31d8f..1f1dd6e4c3 100644 --- a/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULParallelCacheTest.java +++ b/oracles/filters/cache/src/test/java/de/learnlib/filter/cache/parallelism/SULParallelCacheTest.java @@ -17,24 +17,23 @@ import java.util.Random; -import de.learnlib.api.SUL; -import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; import de.learnlib.driver.util.MealySimulatorSUL; -import de.learnlib.filter.cache.LearningCacheOracle.MealyLearningCacheOracle; -import de.learnlib.filter.cache.SULLearningCacheOracle; import de.learnlib.filter.cache.sul.SULCache; +import de.learnlib.filter.cache.sul.SULCaches; +import de.learnlib.filter.statistic.sul.ResetCounterSUL; +import de.learnlib.oracle.parallelism.ParallelOracleBuilders; import net.automatalib.automata.transducers.MealyMachine; import net.automatalib.util.automata.random.RandomAutomata; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; -import net.automatalib.words.WordBuilder; import net.automatalib.words.impl.Alphabets; /** * @author frohme */ public class SULParallelCacheTest - extends AbstractParallelCacheTest, Character, Word> { + extends AbstractParallelCacheTest, SULCache, MealyMachine, Character, Word> { @Override protected Alphabet getAlphabet() { @@ -42,64 +41,28 @@ protected Alphabet getAlphabet() { } @Override - protected MealyMachine getTargetModel() { + protected MealyMachine getTargetModel(Alphabet alphabet) { return RandomAutomata.randomMealy(new Random(42), MODEL_SIZE, getAlphabet(), getAlphabet()); } @Override - protected MealyLearningCacheOracle getCache(Alphabet alphabet, - MembershipOracle> oracle) { - return SULLearningCacheOracle.fromSULCache(SULCache.createTreeCache(alphabet, - new TestSUL<>(getTargetModel(), oracle))); + protected ResetCounterSUL getSUL(MealyMachine targetModel) { + return new ResetCounterSUL<>("Queries", new MealySimulatorSUL<>(targetModel)); } - protected static class TestSUL extends MealySimulatorSUL { - - private final WordBuilder wb; - private final MembershipOracle> oracle; - private final MealyMachine mealyMachine; - private final boolean fork; - - TestSUL(MealyMachine mealy, MembershipOracle> oracle) { - this(mealy, oracle, true); - } - - TestSUL(MealyMachine mealy, MembershipOracle> oracle, boolean fork) { - super(mealy); - this.mealyMachine = mealy; - this.oracle = oracle; - this.fork = fork; - this.wb = new WordBuilder<>(); - } - - @Override - public void pre() { - wb.clear(); - super.pre(); - } - - @Override - public O step(I in) { - wb.add(in); - return super.step(in); - } - - @Override - public void post() { - // query oracle to update counters - oracle.answerQuery(wb.toWord()); - wb.clear(); - super.post(); - } + @Override + protected SULCache getCache(Alphabet alphabet, + ResetCounterSUL sul) { + return SULCaches.createCache(alphabet, sul); + } - @Override - public boolean canFork() { - return fork; - } + @Override + protected ParallelOracle> getParallelOracle(SULCache cache) { + return ParallelOracleBuilders.newDynamicParallelOracle(cache).create(); + } - @Override - public SUL fork() { - return new TestSUL<>(mealyMachine, oracle, fork); - } + @Override + protected int getNumberOfQueries(ResetCounterSUL model) { + return (int) model.getStatisticalData().getCount(); } } diff --git a/oracles/filters/pom.xml b/oracles/filters/pom.xml index 6c5af47807..cdb8816a98 100644 --- a/oracles/filters/pom.xml +++ b/oracles/filters/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/filters/reuse/pom.xml b/oracles/filters/reuse/pom.xml index 86216df755..0896a1679f 100644 --- a/oracles/filters/reuse/pom.xml +++ b/oracles/filters/reuse/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-filters-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/BoundedDeque.java b/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/BoundedDeque.java index 03559b8d29..340753966b 100644 --- a/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/BoundedDeque.java +++ b/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/BoundedDeque.java @@ -21,6 +21,8 @@ import java.util.Deque; import java.util.Iterator; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * A generic deque-derived container which transparently acts either as a stack or a queue, and optionally * a capacity restriction with a configurable policy which element is evicted (or reject) if the maximum capacity is @@ -82,7 +84,7 @@ public BoundedDeque(int capacity, AccessPolicy accessPolicy, EvictPolicy evictPo * * @return the evicted element, {@code null} if the maximum capacity has not been reached */ - public E insert(E element) { + public @Nullable E insert(E element) { E evicted = null; if (size() >= capacity) { if (evictPolicy == EvictPolicy.REJECT_NEW) { @@ -110,7 +112,6 @@ private E evict() { default: throw new IllegalStateException("Illegal evict policy: " + evictPolicy); } - } /** diff --git a/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/ReuseNode.java b/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/ReuseNode.java index 4b7c7a2d33..ac1acca002 100644 --- a/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/ReuseNode.java +++ b/oracles/filters/reuse/src/main/java/de/learnlib/filter/reuse/tree/ReuseNode.java @@ -66,7 +66,7 @@ public S fetchSystemState(boolean remove) { return systemStates.peek(); } - public S addSystemState(S state) { + public @Nullable S addSystemState(S state) { return systemStates.insert(state); } diff --git a/oracles/filters/statistics/pom.xml b/oracles/filters/statistics/pom.xml index 428f1cd8d4..4051688ae8 100644 --- a/oracles/filters/statistics/pom.xml +++ b/oracles/filters/statistics/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-filters-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/oracles/membership-oracles/pom.xml b/oracles/membership-oracles/pom.xml index 3401872e82..ca784ee494 100644 --- a/oracles/membership-oracles/pom.xml +++ b/oracles/membership-oracles/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -55,6 +55,11 @@ limitations under the License. automata-core + + org.checkerframework + checker-qual + + de.learnlib diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/AbstractSULOmegaOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/AbstractSULOmegaOracle.java index 7884aa8db7..15f8933d18 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/AbstractSULOmegaOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/AbstractSULOmegaOracle.java @@ -38,7 +38,7 @@ * After some symbols (i.e. after {@link OmegaQuery#getPrefix()}, and after each {@link OmegaQuery#getLoop()}) the state * of the {@link ObservableSUL} is retrieved, and used to answer the query. * - * Like {@link SULOracle} this class is thread-safe. + * This class is not thread-safe. * * @author Jeroen Meijer * @@ -50,15 +50,9 @@ public abstract class AbstractSULOmegaOracle implements MealyOmegaMembershipOracle { private final ObservableSUL sul; - private final @Nullable ThreadLocal> localSul; protected AbstractSULOmegaOracle(ObservableSUL sul) { this.sul = sul; - if (sul.canFork()) { - this.localSul = ThreadLocal.withInitial(sul::fork); - } else { - this.localSul = null; - } } /** @@ -67,34 +61,21 @@ protected AbstractSULOmegaOracle(ObservableSUL sul) { * @return the {@link ObservableSUL}. */ public ObservableSUL getSul() { - if (localSul != null) { - return localSul.get(); - } else { - return sul; - } + return sul; } @Override public void processQueries(Collection>> queries) { - if (localSul != null) { - processQueries(localSul.get(), queries); - } else { - synchronized (sul) { - processQueries(sul, queries); - } - } - } - - private void processQueries(ObservableSUL sul, Collection>> queries) { for (OmegaQuery> q : queries) { - final Pair, Integer> output = answerQuery(sul, q.getPrefix(), q.getLoop(), q.getRepeat()); + final Pair<@Nullable Word, Integer> output = answerQuery(q.getPrefix(), q.getLoop(), q.getRepeat()); q.answer(output.getFirst(), output.getSecond()); } } protected abstract Q getQueryState(ObservableSUL sul); - private Pair, Integer> answerQuery(ObservableSUL sul, Word prefix, Word loop, int repeat) { + @Override + public Pair<@Nullable Word, Integer> answerQuery(Word prefix, Word loop, int repeat) { assert repeat > 0; sul.pre(); try { diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULOracle.java index 5811da4a4e..c1aea6a9a1 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULOracle.java @@ -22,16 +22,11 @@ import de.learnlib.api.query.Query; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; -import org.checkerframework.checker.nullness.qual.Nullable; /** * A wrapper around a system under learning (SUL). *

    - * This membership oracle is thread-safe. Thread-safety is obtained in either of the following ways:

    + * This membership oracle is not thread-safe. * * @author Falk Howar * @author Malte Isberner @@ -39,36 +34,21 @@ public class SULOracle implements MealyMembershipOracle { private final SUL sul; - private final @Nullable ThreadLocal> localSul; public SULOracle(SUL sul) { this.sul = sul; - if (sul.canFork()) { - this.localSul = ThreadLocal.withInitial(sul::fork); - } else { - this.localSul = null; - } } @Override public void processQueries(Collection>> queries) { - if (localSul != null) { - processQueries(localSul.get(), queries); - } else { - synchronized (sul) { - processQueries(sul, queries); - } - } - } - - private static void processQueries(SUL sul, Collection>> queries) { for (Query> q : queries) { - Word output = answerQuery(sul, q.getPrefix(), q.getSuffix()); + Word output = answerQuery(q.getPrefix(), q.getSuffix()); q.answer(output); } } - public static Word answerQuery(SUL sul, Word prefix, Word suffix) { + @Override + public Word answerQuery(Word prefix, Word suffix) { sul.pre(); try { // Prefix: Execute symbols, don't record output diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULSymbolQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULSymbolQueryOracle.java index fb6ca2f5f0..0191e41469 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULSymbolQueryOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SULSymbolQueryOracle.java @@ -20,6 +20,14 @@ /** * A wrapper that allows to use a {@link SUL} where a {@link SymbolQueryOracle} is expected. + *

    + * Implementation note: The contract of {@link SymbolQueryOracle} does not make any assumptions about when its + * {@link SymbolQueryOracle#reset() reset} method is called. However, from a {@link SUL} perspective it is desirable to + * call its {@link SUL#post() post} method once querying is done. Therefore, multiple calls to {@code this.}{@link + * SULSymbolQueryOracle#reset()} will {@link SUL#post() close} the underlying {@link SUL} only once, so that the {@link + * SUL} can be shutdown by {@code this} oracle from outside, after the learning process has finished. + *

    + * This oracle is not thread-safe. * * @param * input alphabet type @@ -32,19 +40,35 @@ public class SULSymbolQueryOracle implements SymbolQueryOracle { private final SUL sul; + private boolean preRequired; + private boolean postRequired; + public SULSymbolQueryOracle(final SUL sul) { this.sul = sul; - this.sul.pre(); + this.preRequired = true; } @Override public O query(I i) { - return this.sul.step(i); + if (preRequired) { + this.sul.pre(); + this.preRequired = false; + this.postRequired = true; + } + + return queryInternal(i); } @Override public void reset() { - this.sul.post(); - this.sul.pre(); + if (postRequired) { + this.sul.post(); + this.postRequired = false; + } + this.preRequired = true; + } + + protected O queryInternal(I i) { + return this.sul.step(i); } } diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOmegaOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOmegaOracle.java index 543cdbad9b..3d370a7e9c 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOmegaOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOmegaOracle.java @@ -41,6 +41,9 @@ /** * Answers {@link OmegaQuery}s by simulating an automaton. + *

    + * Implementation note: Under the assumption that read-operations do not alter the internal state of the + * automaton, this oracle is thread-safe. * * @author Jeroen Meijer * @@ -50,7 +53,7 @@ * @param the input type. * @param the output type. */ -public class SimulatorOmegaOracle implements SingleQueryOmegaOracle { +public class SimulatorOmegaOracle implements SingleQueryOmegaOracle { /** * The automaton to simulate. @@ -96,7 +99,7 @@ public boolean isSameState(Word input1, S s1, Word input2, S s2) { @Override public void processQueries(Collection> queries) { - MQUtil.answerOmegaQueriesAuto(this, queries); + MQUtil.answerOmegaQueries(this, queries); } /** @@ -108,7 +111,7 @@ public void processQueries(Collection> queries) { * @see OmegaQueryAnswerer#answerQuery(Word, Word, int) */ @Override - public Pair answerQuery(Word prefix, Word loop, int repeat) { + public Pair<@Nullable D, Integer> answerQuery(Word prefix, Word loop, int repeat) { assert repeat > 0; final List states = new ArrayList<>(repeat + 1); @@ -146,7 +149,7 @@ public Pair answerQuery(Word prefix, Word loop, int repeat) { return Pair.of(null, -1); } - public static class DFASimulatorOmegaOracle + public static class DFASimulatorOmegaOracle extends SimulatorOmegaOracle implements SingleQueryOmegaOracleDFA { @@ -163,7 +166,7 @@ public DFAMembershipOracle getMembershipOracle() { } } - public static class MealySimulatorOmegaOracle + public static class MealySimulatorOmegaOracle extends SimulatorOmegaOracle> implements SingleQueryOmegaOracleMealy { diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOracle.java index 8ae1ba9e7c..aaf39f37e6 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/SimulatorOracle.java @@ -28,6 +28,9 @@ /** * A membership oracle backed by an automaton. The automaton must implement the {@link SuffixOutput} concept, allowing * to identify a suffix part in the output (relative to a prefix/suffix subdivision in the input). + *

    + * Implementation note: Under the assumption that read-operations do not alter the internal state of the + * automaton, this oracle is thread-safe. * * @param * input symbol type @@ -57,7 +60,7 @@ public D answerQuery(Word prefix, Word suffix) { @Override public void processQueries(Collection> queries) { - MQUtil.answerQueriesAuto(this, queries); + MQUtil.answerQueries(this, queries); } public static class DFASimulatorOracle extends SimulatorOracle implements SingleQueryOracleDFA { diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULOracle.java index e06c7c3705..304047c1a9 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULOracle.java @@ -23,43 +23,34 @@ import de.learnlib.api.query.Query; import net.automatalib.words.Word; import net.automatalib.words.WordBuilder; -import org.checkerframework.checker.nullness.qual.Nullable; +/** + * A wrapper around a system under learning (SUL) with state local inputs. + *

    + * This membership oracle is not thread-safe. + * + * @author frohme + */ public class StateLocalInputSULOracle implements MealyMembershipOracle { private final StateLocalInputSUL sul; private final O undefinedOutput; - private final @Nullable ThreadLocal> localSul; public StateLocalInputSULOracle(StateLocalInputSUL sul, O undefinedOutput) { this.sul = sul; this.undefinedOutput = undefinedOutput; - if (sul.canFork()) { - this.localSul = ThreadLocal.withInitial(sul::fork); - } else { - this.localSul = null; - } } @Override public void processQueries(Collection>> queries) { - if (localSul != null) { - processQueries(localSul.get(), queries); - } else { - synchronized (sul) { - processQueries(sul, queries); - } - } - } - - private void processQueries(StateLocalInputSUL sul, Collection>> queries) { for (Query> q : queries) { - Word output = answerQuery(sul, q.getPrefix(), q.getSuffix()); + Word output = answerQuery(q.getPrefix(), q.getSuffix()); q.answer(output); } } - private Word answerQuery(StateLocalInputSUL sul, Word prefix, Word suffix) { + @Override + public Word answerQuery(Word prefix, Word suffix) { try { sul.pre(); Collection enabledInputs = sul.currentlyEnabledInputs(); diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracle.java new file mode 100644 index 0000000000..c6b504ab70 --- /dev/null +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracle.java @@ -0,0 +1,71 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.membership; + +import java.util.Collection; +import java.util.Collections; + +import de.learnlib.api.StateLocalInputSUL; +import de.learnlib.api.oracle.SymbolQueryOracle; + +/** + * A {@link SymbolQueryOracle} wrapper for {@link StateLocalInputSUL}s. See {@link SULSymbolQueryOracle}. + *

    + * This oracle is not thread-safe. + * + * @param + * input symbol type + * @param + * output symbol type + * + * @author bainczyk + * @author frohme + * @see SULSymbolQueryOracle + */ +public class StateLocalInputSULSymbolQueryOracle extends SULSymbolQueryOracle + implements SymbolQueryOracle { + + private final StateLocalInputSUL sul; + private final O undefinedOutput; + + private boolean fetchRequired; + + public StateLocalInputSULSymbolQueryOracle(StateLocalInputSUL sul, O undefinedOutput) { + super(sul); + this.sul = sul; + this.undefinedOutput = undefinedOutput; + this.fetchRequired = true; + } + + @Override + public void reset() { + super.reset(); + this.fetchRequired = true; + } + + @Override + protected O queryInternal(I i) { + final Collection enabledInputs = this.fetchRequired ? sul.currentlyEnabledInputs() : Collections.emptyList(); + + if (enabledInputs.contains(i)) { + return sul.step(i); + } else { + this.fetchRequired = false; + return undefinedOutput; + } + } +} diff --git a/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SULSymbolQueryOracleTest.java b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SULSymbolQueryOracleTest.java new file mode 100644 index 0000000000..1152d524c3 --- /dev/null +++ b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SULSymbolQueryOracleTest.java @@ -0,0 +1,92 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.membership; + +import java.util.Random; + +import de.learnlib.api.SUL; +import de.learnlib.driver.util.MealySimulatorSUL; +import de.learnlib.examples.mealy.ExampleRandomMealy; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * @author frohme + */ +public class SULSymbolQueryOracleTest { + + private ExampleRandomMealy example; + private SUL sul; + + @BeforeClass + public void setUp() { + final Alphabet inputs = Alphabets.characters('a', 'c'); + final Alphabet outputs = Alphabets.integers(0, 2); + example = ExampleRandomMealy.createExample(new Random(42), inputs, 10, outputs.toArray(new Integer[0])); + sul = new MealySimulatorSUL<>(example.getReferenceAutomaton()); + } + + @Test + public void testResetIdempotency() { + final SUL mock = Mockito.spy(sul); + + final SULSymbolQueryOracle oracle = new SULSymbolQueryOracle<>(mock); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + + oracle.reset(); + oracle.reset(); + oracle.reset(); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + } + + @Test + public void testQueriesAndCleanUp() { + final SUL mock = Mockito.spy(sul); + + final SULSymbolQueryOracle oracle = new SULSymbolQueryOracle<>(mock); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + + final Word i1 = Word.fromCharSequence("abcabcabc"); + final Word o1 = oracle.answerQuery(i1); + oracle.reset(); // cleanup + + Assert.assertEquals(o1, example.getReferenceAutomaton().computeOutput(i1)); + Mockito.verify(mock, Mockito.times(1)).pre(); + Mockito.verify(mock, Mockito.times(1)).post(); + Mockito.verify(mock, Mockito.times(i1.size())).step(Mockito.anyChar()); + + final Word i2 = Word.fromCharSequence("cba"); + final Word o2 = oracle.answerQuery(i2); + oracle.reset(); // cleanup + oracle.reset(); // twice + + Assert.assertEquals(o2, example.getReferenceAutomaton().computeOutput(i2)); + Mockito.verify(mock, Mockito.times(2)).pre(); + Mockito.verify(mock, Mockito.times(2)).post(); + Mockito.verify(mock, Mockito.times(i1.size() + i2.size())).step(Mockito.anyChar()); + } +} diff --git a/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracleTest.java b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracleTest.java new file mode 100644 index 0000000000..a5a1f4489c --- /dev/null +++ b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/StateLocalInputSULSymbolQueryOracleTest.java @@ -0,0 +1,107 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.membership; + +import java.util.Collections; +import java.util.Random; + +import de.learnlib.api.StateLocalInputSUL; +import de.learnlib.driver.util.StateLocalInputMealySimulatorSUL; +import de.learnlib.examples.mealy.ExampleRandomStateLocalInputMealy; +import net.automatalib.words.Alphabet; +import net.automatalib.words.Word; +import net.automatalib.words.impl.Alphabets; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * @author frohme + */ +public class StateLocalInputSULSymbolQueryOracleTest { + + private ExampleRandomStateLocalInputMealy example; + private StateLocalInputSUL sul; + + @BeforeClass + public void setUp() { + final Alphabet inputs = Alphabets.characters('a', 'c'); + final Alphabet outputs = Alphabets.integers(0, 2); + example = ExampleRandomStateLocalInputMealy.createExample(new Random(42), + inputs, + 10, + -1, + outputs.toArray(new Integer[0])); + sul = new StateLocalInputMealySimulatorSUL<>(example.getReferenceAutomaton()); + } + + @Test + public void testResetIdempotency() { + final StateLocalInputSUL mock = Mockito.spy(sul); + Mockito.doAnswer(invocation -> Collections.singleton('a')).when(mock).currentlyEnabledInputs(); + + final SULSymbolQueryOracle oracle = + new StateLocalInputSULSymbolQueryOracle<>(mock, example.getUndefinedOutput()); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + Mockito.verify(mock, Mockito.times(0)).currentlyEnabledInputs(); + + oracle.reset(); + oracle.reset(); + oracle.reset(); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + Mockito.verify(mock, Mockito.times(0)).currentlyEnabledInputs(); + } + + @Test + public void testQueriesAndCleanUp() { + final StateLocalInputSUL mock = Mockito.spy(sul); + Mockito.doAnswer(invocation -> Collections.singleton('a')).when(mock).currentlyEnabledInputs(); + + final SULSymbolQueryOracle oracle = + new StateLocalInputSULSymbolQueryOracle<>(mock, example.getUndefinedOutput()); + + Mockito.verify(mock, Mockito.times(0)).pre(); + Mockito.verify(mock, Mockito.times(0)).post(); + + final Word i1 = Word.fromCharSequence("abcabcabc"); + final Word o1 = oracle.answerQuery(i1); + oracle.reset(); // cleanup + + Assert.assertEquals(o1.firstSymbol(), example.getReferenceAutomaton().computeOutput(i1).firstSymbol()); + Assert.assertEquals(o1.subWord(1), + Word.fromList(Collections.nCopies(i1.size() - 1, example.getUndefinedOutput()))); + Mockito.verify(mock, Mockito.times(1)).pre(); + Mockito.verify(mock, Mockito.times(1)).post(); + Mockito.verify(mock, Mockito.times(2)).currentlyEnabledInputs(); + Mockito.verify(mock, Mockito.times(1)).step(Mockito.anyChar()); + + final Word i2 = Word.fromCharSequence("aaaaa"); + final Word o2 = oracle.answerQuery(i2); + oracle.reset(); // cleanup + oracle.reset(); // twice + + Assert.assertEquals(o2, example.getReferenceAutomaton().computeOutput(i2)); + Mockito.verify(mock, Mockito.times(2)).pre(); + Mockito.verify(mock, Mockito.times(2)).post(); + Mockito.verify(mock, Mockito.times(2 + i2.size())).currentlyEnabledInputs(); + Mockito.verify(mock, Mockito.times(1 + i2.size())).step(Mockito.anyChar()); + } +} diff --git a/oracles/parallelism/pom.xml b/oracles/parallelism/pom.xml index 92714a39de..f2de6db2d6 100644 --- a/oracles/parallelism/pom.xml +++ b/oracles/parallelism/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -36,6 +36,10 @@ limitations under the License. ${project.groupId} learnlib-api + + ${project.groupId} + learnlib-membership-oracles + ${project.groupId} learnlib-settings @@ -66,6 +70,11 @@ limitations under the License. + + org.mockito + mockito-core + + org.testng testng diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessor.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessor.java new file mode 100644 index 0000000000..7a9230a213 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessor.java @@ -0,0 +1,132 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Supplier; + +import com.google.common.base.Throwables; +import de.learnlib.api.oracle.parallelism.BatchInterruptedException; +import de.learnlib.api.oracle.parallelism.BatchProcessor; +import de.learnlib.api.oracle.parallelism.ThreadPool; +import de.learnlib.setting.LearnLibProperty; +import de.learnlib.setting.LearnLibSettings; +import org.checkerframework.checker.index.qual.NonNegative; + +/** + * A batch processor that dynamically distributes queries to worker threads. + * + * @param + * query type + * @param

    + * (sub-) processor type + * + * @author Malte Isberner + */ +public abstract class AbstractDynamicBatchProcessor> + implements ThreadPool, BatchProcessor { + + public static final int BATCH_SIZE; + public static final int POOL_SIZE; + public static final PoolPolicy POOL_POLICY; + + static { + LearnLibSettings settings = LearnLibSettings.getInstance(); + + int numProcessors = Runtime.getRuntime().availableProcessors(); + + BATCH_SIZE = settings.getInt(LearnLibProperty.PARALLEL_BATCH_SIZE_DYNAMIC, 1); + POOL_SIZE = settings.getInt(LearnLibProperty.PARALLEL_POOL_SIZE, numProcessors); + POOL_POLICY = settings.getEnumValue(LearnLibProperty.PARALLEL_POOL_POLICY, PoolPolicy.class, PoolPolicy.CACHED); + } + + private final ThreadLocal

    threadLocalOracle; + private final ExecutorService executor; + private final @NonNegative int batchSize; + + public AbstractDynamicBatchProcessor(final Supplier oracleSupplier, + @NonNegative int batchSize, + ExecutorService executor) { + this.threadLocalOracle = ThreadLocal.withInitial(oracleSupplier); + this.executor = executor; + this.batchSize = batchSize; + } + + @Override + public void shutdown() { + executor.shutdown(); + } + + @Override + public void shutdownNow() { + executor.shutdownNow(); + } + + @Override + public void processBatch(Collection queries) { + if (queries.isEmpty()) { + return; + } + + int numQueries = queries.size(); + int numJobs = (numQueries - 1) / batchSize + 1; + List currentBatch = null; + + List> futures = new ArrayList<>(numJobs); + + for (Q query : queries) { + + if (currentBatch == null) { + currentBatch = new ArrayList<>(batchSize); + } + + currentBatch.add(query); + if (currentBatch.size() == batchSize) { + Future future = executor.submit(new DynamicQueriesJob<>(currentBatch, threadLocalOracle)); + futures.add(future); + currentBatch = null; + } + } + + if (currentBatch != null) { + Future future = executor.submit(new DynamicQueriesJob<>(currentBatch, threadLocalOracle)); + futures.add(future); + } + + try { + // Await completion of all jobs + for (Future future : futures) { + future.get(); + } + } catch (ExecutionException e) { + Throwables.throwIfUnchecked(e.getCause()); + throw new AssertionError("Runnables must not throw checked exceptions", e); + } catch (InterruptedException e) { + Thread.interrupted(); + throw new BatchInterruptedException(e); + } + } + + protected P getProcessor() { + return threadLocalOracle.get(); + } + +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessorBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessorBuilder.java new file mode 100644 index 0000000000..db19180d76 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractDynamicBatchProcessorBuilder.java @@ -0,0 +1,141 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import com.google.common.base.Preconditions; +import de.learnlib.api.oracle.parallelism.BatchProcessor; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import net.automatalib.commons.util.concurrent.ScalingThreadPoolExecutor; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Builder class for a {@link AbstractDynamicBatchProcessor}. + * + * @param + * query type + * @param

    + * (sub-) processor type + * @param + * constructed oracle type + * + * @author Malte Isberner + */ +public abstract class AbstractDynamicBatchProcessorBuilder, OR> { + + private static final int DEFAULT_KEEP_ALIVE_TIME = 60; + + private final @Nullable Supplier oracleSupplier; + private final @Nullable Collection oracles; + private ExecutorService customExecutor; + private @NonNegative int batchSize = AbstractDynamicBatchProcessor.BATCH_SIZE; + private @NonNegative int poolSize = AbstractDynamicBatchProcessor.POOL_SIZE; + private PoolPolicy poolPolicy = AbstractDynamicBatchProcessor.POOL_POLICY; + + public AbstractDynamicBatchProcessorBuilder(Supplier oracleSupplier) { + this.oracleSupplier = oracleSupplier; + this.oracles = null; + } + + public AbstractDynamicBatchProcessorBuilder(Collection oracles) { + Preconditions.checkArgument(!oracles.isEmpty(), "No oracles specified"); + this.oracles = oracles; + this.oracleSupplier = null; + } + + public AbstractDynamicBatchProcessorBuilder withCustomExecutor(ExecutorService executor) { + this.customExecutor = executor; + return this; + } + + public AbstractDynamicBatchProcessorBuilder withBatchSize(int batchSize) { + this.batchSize = batchSize; + return this; + } + + public AbstractDynamicBatchProcessorBuilder withPoolSize(@NonNegative int poolSize) { + this.poolSize = poolSize; + return this; + } + + public AbstractDynamicBatchProcessorBuilder withPoolPolicy(PoolPolicy policy) { + this.poolPolicy = policy; + return this; + } + + public OR create() { + + final Supplier supplier; + final ExecutorService executor; + + if (oracles != null) { + executor = Executors.newFixedThreadPool(oracles.size()); + supplier = new StaticOracleProvider<>(oracles); + } else if (customExecutor != null) { + executor = customExecutor; + supplier = oracleSupplier; + } else { + switch (poolPolicy) { + case FIXED: + executor = Executors.newFixedThreadPool(poolSize); + break; + case CACHED: + executor = new ScalingThreadPoolExecutor(0, poolSize, DEFAULT_KEEP_ALIVE_TIME, TimeUnit.SECONDS); + break; + default: + throw new IllegalStateException("Unknown pool policy: " + poolPolicy); + } + supplier = oracleSupplier; + } + + return buildOracle(supplier, batchSize, executor); + } + + protected abstract OR buildOracle(Supplier supplier, int batchSize, ExecutorService executorService); + + static class StaticOracleProvider

    > implements Supplier

    { + + private final P[] oracles; + private int idx; + + StaticOracleProvider(P[] oracles) { + this.oracles = oracles; + } + + @SuppressWarnings("unchecked") + StaticOracleProvider(Collection oracles) { + this.oracles = oracles.toArray((P[]) new BatchProcessor[oracles.size()]); + } + + @Override + public P get() { + synchronized (this) { + if (idx < oracles.length) { + return oracles[idx++]; + } + } + + throw new IllegalStateException( + "The supplier should not have been called more than " + oracles.length + " times"); + } + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractQueriesJob.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractQueriesJob.java index a55fbe4e84..c358db1aa1 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractQueriesJob.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractQueriesJob.java @@ -17,35 +17,32 @@ import java.util.Collection; -import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.api.query.Query; +import de.learnlib.api.oracle.parallelism.BatchProcessor; /** * Abstract base class for jobs (i.e., {@link Runnable}s) that process queries. *

    - * Subclasses specify how the delegate oracle is obtained. + * Subclasses specify how the delegate batch processor is obtained. * - * @param - * input symbol type - * @param - * output domain type + * @param + * query type * * @author Malte Isberner */ -abstract class AbstractQueriesJob implements Runnable { +abstract class AbstractQueriesJob implements Runnable { - private final Collection> queries; + private final Collection queries; - AbstractQueriesJob(Collection> queries) { + AbstractQueriesJob(Collection queries) { this.queries = queries; } @Override public void run() { - MembershipOracle oracle = getOracle(); + BatchProcessor oracle = getOracle(); - oracle.processQueries(queries); + oracle.processBatch(queries); } - protected abstract MembershipOracle getOracle(); + protected abstract BatchProcessor getOracle(); } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessor.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessor.java new file mode 100644 index 0000000000..e9a6c9126e --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessor.java @@ -0,0 +1,176 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import com.google.common.base.Throwables; +import de.learnlib.api.oracle.parallelism.BatchInterruptedException; +import de.learnlib.api.oracle.parallelism.BatchProcessor; +import de.learnlib.api.oracle.parallelism.ThreadPool; +import de.learnlib.setting.LearnLibProperty; +import de.learnlib.setting.LearnLibSettings; +import net.automatalib.commons.smartcollections.ArrayStorage; +import org.checkerframework.checker.index.qual.NonNegative; + +/** + * A batch processor that statically distributes a set of queries among several threads. + *

    + * An incoming set of queries is divided into a given number of batches, such that the sizes of all batches differ by at + * most one. This keeps the required synchronization effort low, but if some batches are "harder" (for whatever reason) + * than others, the load can be very unbalanced. + * + * @param + * query type + * @param

    + * (sub-) processor type + * + * @author Malte Isberner + */ +public abstract class AbstractStaticBatchProcessor> + implements ThreadPool, BatchProcessor { + + private static final int DEFAULT_MIN_BATCH_SIZE = 10; + public static final int MIN_BATCH_SIZE; + public static final int NUM_INSTANCES; + public static final PoolPolicy POOL_POLICY; + + static { + LearnLibSettings settings = LearnLibSettings.getInstance(); + + int numCores = Runtime.getRuntime().availableProcessors(); + + MIN_BATCH_SIZE = settings.getInt(LearnLibProperty.PARALLEL_BATCH_SIZE_STATIC, DEFAULT_MIN_BATCH_SIZE); + NUM_INSTANCES = settings.getInt(LearnLibProperty.PARALLEL_POOL_SIZE, numCores); + POOL_POLICY = settings.getEnumValue(LearnLibProperty.PARALLEL_POOL_SIZE, PoolPolicy.class, PoolPolicy.CACHED); + } + + private final @NonNegative int minBatchSize; + private final ArrayStorage

    oracles; + private final ExecutorService executor; + + public AbstractStaticBatchProcessor(Collection oracles, + @NonNegative int minBatchSize, + PoolPolicy policy) { + + this.oracles = new ArrayStorage<>(oracles); + + switch (policy) { + case FIXED: + this.executor = Executors.newFixedThreadPool(this.oracles.size() - 1); + break; + case CACHED: + this.executor = Executors.newCachedThreadPool(); + break; + default: + throw new IllegalArgumentException("Illegal pool policy: " + policy); + } + this.minBatchSize = minBatchSize; + } + + @Override + public void processBatch(Collection queries) { + int num = queries.size(); + if (num <= 0) { + return; + } + + int numBatches = (num - minBatchSize) / minBatchSize + 1; + if (numBatches > oracles.size()) { + numBatches = oracles.size(); + } + + // One batch is always executed in the local thread. This saves the thread creation + // overhead for the common case where the batch size is quite small. + int externalBatches = numBatches - 1; + + if (externalBatches == 0) { + processQueriesLocally(queries); + return; + } + + // Calculate the number of full and non-full batches. The difference in size + // will never exceed one (cf. pidgeonhole principle) + int fullBatchSize = (num - 1) / numBatches + 1; + int nonFullBatches = fullBatchSize * numBatches - num; + + List> futures = new ArrayList<>(externalBatches); + + Iterator queryIt = queries.iterator(); + + // Start the threads for the external batches + for (int i = 0; i < externalBatches; i++) { + int bs = fullBatchSize; + if (i < nonFullBatches) { + bs--; + } + List batch = new ArrayList<>(bs); + for (int j = 0; j < bs; j++) { + batch.add(queryIt.next()); + } + + Runnable job = new StaticQueriesJob<>(batch, oracles.get(i + 1)); + Future future = executor.submit(job); + futures.add(future); + } + + // Finally, prepare and process the batch for the oracle executed in this thread. + List localBatch = new ArrayList<>(fullBatchSize); + for (int j = 0; j < fullBatchSize; j++) { + localBatch.add(queryIt.next()); + } + + processQueriesLocally(localBatch); + + try { + for (Future f : futures) { + f.get(); + } + } catch (ExecutionException ex) { + Throwables.throwIfUnchecked(ex.getCause()); + throw new AssertionError("Runnable must not throw checked exceptions", ex); + } catch (InterruptedException ex) { + Thread.interrupted(); + throw new BatchInterruptedException(ex); + } + } + + private void processQueriesLocally(Collection localBatch) { + oracles.get(0).processBatch(localBatch); + } + + @Override + public void shutdown() { + executor.shutdown(); + } + + @Override + public void shutdownNow() { + executor.shutdownNow(); + } + + protected P getProcessor() { + return oracles.get(0); + } + +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessorBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessorBuilder.java new file mode 100644 index 0000000000..10b7cd2c2d --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/AbstractStaticBatchProcessorBuilder.java @@ -0,0 +1,93 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +import com.google.common.base.Preconditions; +import de.learnlib.api.oracle.parallelism.BatchProcessor; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A builder for a {@link AbstractStaticBatchProcessor}. + * + * @param + * query type + * @param

    + * (sub-) processor type + * @param + * constructed oracle type + * + * @author Malte Isberner + */ +public abstract class AbstractStaticBatchProcessorBuilder, OR> { + + private final @Nullable Collection oracles; + private final @Nullable Supplier oracleSupplier; + private @NonNegative int minBatchSize = AbstractStaticBatchProcessor.MIN_BATCH_SIZE; + private @NonNegative int numInstances = AbstractStaticBatchProcessor.NUM_INSTANCES; + private PoolPolicy poolPolicy = AbstractStaticBatchProcessor.POOL_POLICY; + + public AbstractStaticBatchProcessorBuilder(Collection oracles) { + Preconditions.checkArgument(!oracles.isEmpty(), "No oracles specified"); + this.oracles = oracles; + this.oracleSupplier = null; + } + + public AbstractStaticBatchProcessorBuilder(Supplier oracleSupplier) { + this.oracles = null; + this.oracleSupplier = oracleSupplier; + } + + public AbstractStaticBatchProcessorBuilder withMinBatchSize(@NonNegative int minBatchSize) { + this.minBatchSize = minBatchSize; + return this; + } + + public AbstractStaticBatchProcessorBuilder withPoolPolicy(PoolPolicy policy) { + this.poolPolicy = policy; + return this; + } + + public AbstractStaticBatchProcessorBuilder withNumInstances(@NonNegative int numInstances) { + this.numInstances = numInstances; + return this; + } + + @SuppressWarnings("nullness") // the constructors guarantee that oracles and oracleSupplier are null exclusively + public OR create() { + Collection oracleInstances; + if (oracles != null) { + oracleInstances = oracles; + } else { + List

    oracleList = new ArrayList<>(numInstances); + for (int i = 0; i < numInstances; i++) { + oracleList.add(oracleSupplier.get()); + } + oracleInstances = oracleList; + } + + return buildOracle(oracleInstances, minBatchSize, poolPolicy); + } + + protected abstract OR buildOracle(Collection oracleInstances, int minBatchSize, PoolPolicy poolPolicy); + +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracle.java new file mode 100644 index 0000000000..c8e0ea0eca --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracle.java @@ -0,0 +1,62 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.OmegaMembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOmegaOracle; +import de.learnlib.api.query.OmegaQuery; +import net.automatalib.words.Word; +import org.checkerframework.checker.index.qual.NonNegative; + +/** + * A specialized {@link AbstractDynamicBatchProcessor} for {@link OmegaMembershipOracle}s that implements {@link + * ParallelOmegaOracle}. + * + * @param + * input symbol type + * @param + * output domain type + */ +public class DynamicParallelOmegaOracle + extends AbstractDynamicBatchProcessor, OmegaMembershipOracle> + implements ParallelOmegaOracle { + + public DynamicParallelOmegaOracle(Supplier> oracleSupplier, + @NonNegative int batchSize, + ExecutorService executor) { + super(oracleSupplier, batchSize, executor); + } + + @Override + public void processQueries(Collection> omegaQueries) { + processBatch(omegaQueries); + } + + @Override + public MembershipOracle getMembershipOracle() { + return getProcessor().getMembershipOracle(); + } + + @Override + public boolean isSameState(Word w1, S s1, Word w2, S s2) { + return getProcessor().isSameState(w1, s1, w2, s2); + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleBuilder.java new file mode 100644 index 0000000000..1cfbb61419 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleBuilder.java @@ -0,0 +1,52 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import de.learnlib.api.oracle.OmegaMembershipOracle; +import de.learnlib.api.query.OmegaQuery; + +/** + * A specialized {@link AbstractDynamicBatchProcessorBuilder} for {@link OmegaMembershipOracle}s. + * + * @param + * oracle state type + * @param + * input symbol type + * @param + * output domain type + */ +public class DynamicParallelOmegaOracleBuilder + extends AbstractDynamicBatchProcessorBuilder, OmegaMembershipOracle, DynamicParallelOmegaOracle> { + + public DynamicParallelOmegaOracleBuilder(Supplier> oracleSupplier) { + super(oracleSupplier); + } + + public DynamicParallelOmegaOracleBuilder(Collection> oracles) { + super(oracles); + } + + @Override + protected DynamicParallelOmegaOracle buildOracle(Supplier> supplier, + int batchSize, + ExecutorService executorService) { + return new DynamicParallelOmegaOracle<>(supplier, batchSize, executorService); + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracle.java index f4a3754d64..c17dd49881 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracle.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracle.java @@ -15,112 +15,35 @@ */ package de.learnlib.oracle.parallelism; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; import java.util.function.Supplier; -import com.google.common.base.Throwables; import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; import de.learnlib.api.query.Query; -import de.learnlib.setting.LearnLibProperty; -import de.learnlib.setting.LearnLibSettings; import org.checkerframework.checker.index.qual.NonNegative; /** - * A parallel membership oracle that dynamically distributes queries to worker threads. + * A specialized {@link AbstractDynamicBatchProcessor} for {@link MembershipOracle}s that implements {@link + * ParallelOracle}. * * @param * input symbol type * @param * output domain type - * - * @author Malte Isberner */ -public class DynamicParallelOracle implements ParallelOracle { - - public static final int BATCH_SIZE; - public static final int POOL_SIZE; - public static final PoolPolicy POOL_POLICY; - - static { - LearnLibSettings settings = LearnLibSettings.getInstance(); - - int numProcessors = Runtime.getRuntime().availableProcessors(); - - BATCH_SIZE = settings.getInt(LearnLibProperty.PARALLEL_BATCH_SIZE_DYNAMIC, 1); - POOL_SIZE = settings.getInt(LearnLibProperty.PARALLEL_POOL_SIZE, numProcessors); - POOL_POLICY = settings.getEnumValue(LearnLibProperty.PARALLEL_POOL_POLICY, PoolPolicy.class, PoolPolicy.CACHED); - } +public class DynamicParallelOracle extends AbstractDynamicBatchProcessor, MembershipOracle> + implements ParallelOracle { - private final ThreadLocal> threadLocalOracle; - private final ExecutorService executor; - private final @NonNegative int batchSize; - - public DynamicParallelOracle(final Supplier> oracleSupplier, + public DynamicParallelOracle(Supplier> oracleSupplier, @NonNegative int batchSize, ExecutorService executor) { - this.threadLocalOracle = ThreadLocal.withInitial(oracleSupplier); - this.executor = executor; - this.batchSize = batchSize; - } - - @Override - public void shutdown() { - executor.shutdown(); - } - - @Override - public void shutdownNow() { - executor.shutdownNow(); + super(oracleSupplier, batchSize, executor); } @Override public void processQueries(Collection> queries) { - if (queries.isEmpty()) { - return; - } - - int numQueries = queries.size(); - int numJobs = (numQueries - 1) / batchSize + 1; - List> currentBatch = null; - - List> futures = new ArrayList<>(numJobs); - - for (Query query : queries) { - - if (currentBatch == null) { - currentBatch = new ArrayList<>(batchSize); - } - - currentBatch.add(query); - if (currentBatch.size() == batchSize) { - Future future = executor.submit(new DynamicQueriesJob<>(currentBatch, threadLocalOracle)); - futures.add(future); - currentBatch = null; - } - } - - if (currentBatch != null) { - Future future = executor.submit(new DynamicQueriesJob<>(currentBatch, threadLocalOracle)); - futures.add(future); - } - - try { - // Await completion of all jobs - for (Future future : futures) { - future.get(); - } - } catch (ExecutionException e) { - Throwables.throwIfUnchecked(e.getCause()); - throw new AssertionError("Runnables must not throw checked exceptions", e); - } catch (InterruptedException e) { - Thread.interrupted(); - throw new ParallelOracleInterruptedException(e); - } + processBatch(queries); } - } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracleBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracleBuilder.java index 7ef66fe72f..ef391b3402 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracleBuilder.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelOracleBuilder.java @@ -17,126 +17,34 @@ import java.util.Collection; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import com.google.common.base.Preconditions; import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.oracle.parallelism.ParallelOracle.PoolPolicy; -import net.automatalib.commons.smartcollections.ArrayStorage; -import net.automatalib.commons.util.concurrent.ScalingThreadPoolExecutor; -import org.checkerframework.checker.index.qual.NonNegative; -import org.checkerframework.checker.nullness.qual.Nullable; +import de.learnlib.api.query.Query; /** - * Builder class for a {@link DynamicParallelOracle}. + * A specialized {@link AbstractDynamicBatchProcessorBuilder} for {@link MembershipOracle}s. * * @param * input symbol type * @param - * output type - * - * @author Malte Isberner + * output domain type */ -public class DynamicParallelOracleBuilder { - - private static final int DEFAULT_KEEP_ALIVE_TIME = 60; - - private final @Nullable Supplier> oracleSupplier; - private final @Nullable Collection> oracles; - private ExecutorService customExecutor; - private @NonNegative int batchSize = DynamicParallelOracle.BATCH_SIZE; - private @NonNegative int poolSize = DynamicParallelOracle.POOL_SIZE; - private PoolPolicy poolPolicy = DynamicParallelOracle.POOL_POLICY; +public class DynamicParallelOracleBuilder + extends AbstractDynamicBatchProcessorBuilder, MembershipOracle, DynamicParallelOracle> { public DynamicParallelOracleBuilder(Supplier> oracleSupplier) { - this.oracleSupplier = oracleSupplier; - this.oracles = null; + super(oracleSupplier); } public DynamicParallelOracleBuilder(Collection> oracles) { - Preconditions.checkArgument(!oracles.isEmpty(), "No oracles specified"); - this.oracles = oracles; - this.oracleSupplier = null; - } - - public DynamicParallelOracleBuilder withCustomExecutor(ExecutorService executor) { - this.customExecutor = executor; - return this; - } - - public DynamicParallelOracleBuilder withBatchSize(int batchSize) { - this.batchSize = batchSize; - return this; - } - - public DynamicParallelOracleBuilder withPoolSize(@NonNegative int poolSize) { - this.poolSize = poolSize; - return this; - } - - public DynamicParallelOracleBuilder withPoolPolicy(PoolPolicy policy) { - this.poolPolicy = policy; - return this; + super(oracles); } - public DynamicParallelOracle create() { - - final Supplier> supplier; - final ExecutorService executor; - - if (oracles != null) { - executor = Executors.newFixedThreadPool(oracles.size()); - supplier = new StaticOracleProvider<>(new ArrayStorage<>(oracles)); - } else if (customExecutor != null) { - executor = customExecutor; - supplier = oracleSupplier; - } else { - switch (poolPolicy) { - case FIXED: - executor = Executors.newFixedThreadPool(poolSize); - break; - case CACHED: - executor = new ScalingThreadPoolExecutor(0, poolSize, DEFAULT_KEEP_ALIVE_TIME, TimeUnit.SECONDS); - break; - default: - throw new IllegalStateException("Unknown pool policy: " + poolPolicy); - } - supplier = oracleSupplier; - } - - return new DynamicParallelOracle<>(supplier, batchSize, executor); + @Override + protected DynamicParallelOracle buildOracle(Supplier> supplier, + int batchSize, + ExecutorService executorService) { + return new DynamicParallelOracle<>(supplier, batchSize, executorService); } - - static class StaticOracleProvider implements Supplier> { - - private final ArrayStorage> oracles; - private int idx; - - StaticOracleProvider(ArrayStorage> oracles) { - this.oracles = oracles; - } - - StaticOracleProvider(Collection> oracles) { - this.oracles = new ArrayStorage<>(oracles.size()); - int idx = 0; - for (final MembershipOracle oracle : oracles) { - this.oracles.set(idx++, oracle); - } - } - - @Override - public MembershipOracle get() { - synchronized (this) { - if (idx < oracles.size()) { - return oracles.get(idx++); - } - } - - throw new IllegalStateException( - "The supplier should not have been called more than " + oracles.size() + " times"); - } - } - } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicQueriesJob.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicQueriesJob.java index 15f30f388c..e14a766236 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicQueriesJob.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicQueriesJob.java @@ -17,35 +17,31 @@ import java.util.Collection; -import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.api.query.Query; +import de.learnlib.api.oracle.parallelism.BatchProcessor; /** - * A queries job that maintains a thread-local reference to a membership oracle, and dynamically selects that oracle - * depending on the executing thread. + * A queries job that maintains a thread-local reference to a {@link BatchProcessor}, and dynamically selects that + * oracle depending on the executing thread. *

    * Note: This class assumes that the respective {@link ThreadLocal#get()} methods never returns a {@code null} * reference. * - * @param - * input symbol type - * @param - * output domain type + * @param + * query type * * @author Malte Isberner */ -final class DynamicQueriesJob extends AbstractQueriesJob { +final class DynamicQueriesJob extends AbstractQueriesJob { - private final ThreadLocal> threadLocalOracle; + private final ThreadLocal> threadLocalOracle; - DynamicQueriesJob(Collection> queries, - ThreadLocal> threadLocalOracle) { + DynamicQueriesJob(Collection queries, ThreadLocal> threadLocalOracle) { super(queries); this.threadLocalOracle = threadLocalOracle; } @Override - protected MembershipOracle getOracle() { + protected BatchProcessor getOracle() { return threadLocalOracle.get(); } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java index 3a0d5ed942..b7886c29c0 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java @@ -18,9 +18,18 @@ import java.util.Collection; import java.util.function.Supplier; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import de.learnlib.api.ObservableSUL; +import de.learnlib.api.SUL; +import de.learnlib.api.StateLocalInputSUL; import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.oracle.parallelism.ParallelOracle.PoolPolicy; +import de.learnlib.api.oracle.OmegaMembershipOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.oracle.membership.AbstractSULOmegaOracle; +import de.learnlib.oracle.membership.SULOracle; +import de.learnlib.oracle.membership.StateLocalInputSULOracle; +import net.automatalib.words.Word; /** * Builders for (static and dynamic) parallel oracles. @@ -44,11 +53,12 @@ * .withCustomExecutor(myExecutor) * .create(); * + * Note: This requires the shared membership oracle to be thread-safe. *

    - * Creating a dynamic parallel oracle with a cached thread pool of maximum size 4, a batch size of 5, using an oracle - * supplier: + * Creating a dynamic parallel oracle with a cached thread pool of maximum size 4, a batch size of 5, using a (forkabel) + * SUL: *

    - * ParallelOracleBuilders.newDynamicParallelOracle(oracleSupplier)
    + * ParallelOracleBuilders.newDynamicParallelOracle(sul)
      *      .withBatchSize(5)
      *      .withPoolSize(4)
      *      .withPoolPolicy(PoolPolicy.CACHED)
    @@ -59,14 +69,54 @@
      */
     public final class ParallelOracleBuilders {
     
    +    private static final String FORKABLE_SUL_ERR = "SUL must be forkable for parallel processing";
    +
         private ParallelOracleBuilders() {
             throw new AssertionError("Constructor should not be invoked");
         }
     
         /**
    -     * Creates a {@link DynamicParallelOracleBuilder} using the provided supplier. Uses the further specified
    -     * {@link DynamicParallelOracleBuilder#withPoolPolicy(PoolPolicy)} and
    -     * {@link DynamicParallelOracleBuilder#withPoolSize(int)} (or its defaults) to determine the thread pool.
    +     * Creates a {@link DynamicParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that
    +     * the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific membership oracle instances
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  DynamicParallelOracleBuilder> newDynamicParallelOracle(SUL sul) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        return new DynamicParallelOracleBuilder<>(toSupplier(sul));
    +    }
    +
    +    /**
    +     * Creates a {@link DynamicParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that
    +     * the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific membership oracle instances
    +     * @param undefinedInput
    +     *         the input symbol used for responding to inputs that are not {@link StateLocalInputSUL#currentlyEnabledInputs()
    +     *         enabled}.
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  DynamicParallelOracleBuilder> newDynamicParallelOracle(StateLocalInputSUL sul,
    +                                                                                           O undefinedInput) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        return new DynamicParallelOracleBuilder<>(toSupplier(sul, undefinedInput));
    +    }
    +
    +    /**
    +     * Creates a {@link DynamicParallelOracleBuilder} using the provided supplier.
          *
          * @param oracleSupplier
          *         the supplier for spawning new thread-specific membership oracle instances
    @@ -120,9 +170,130 @@ public static  DynamicParallelOracleBuilder newDynamicParallelOracle
         }
     
         /**
    -     * Creates a {@link StaticParallelOracleBuilder} using the provided supplier. Uses the further specified
    -     * {@link StaticParallelOracleBuilder#withPoolPolicy(PoolPolicy)} and
    -     * {@link StaticParallelOracleBuilder#withNumInstances(int)}} (or its defaults) to determine the thread pool.
    +     * Creates a {@link DynamicParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that
    +     * the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific omega membership oracle instances
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  DynamicParallelOmegaOracleBuilder> newDynamicParallelOmegaOracle(ObservableSUL sul) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        // instantiate inner supplier to resolve generics
    +        return new DynamicParallelOmegaOracleBuilder<>(toSupplier(sul)::get);
    +    }
    +
    +    /**
    +     * Creates a {@link DynamicParallelOmegaOracleBuilder} using the provided supplier.
    +     *
    +     * @param oracleSupplier
    +     *         the supplier for spawning new thread-specific membership oracle instances
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  DynamicParallelOmegaOracleBuilder newDynamicParallelOmegaOracle(Supplier> oracleSupplier) {
    +        return new DynamicParallelOmegaOracleBuilder<>(oracleSupplier);
    +    }
    +
    +    /**
    +     * Convenience method for {@link #newDynamicParallelOmegaOracle(Collection)}.
    +     *
    +     * @param firstOracle
    +     *         the first (mandatory) oracle
    +     * @param otherOracles
    +     *         further (optional) oracles to be used by other threads
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    @SafeVarargs
    +    public static  DynamicParallelOmegaOracleBuilder newDynamicParallelOmegaOracle(
    +            OmegaMembershipOracle firstOracle,
    +            OmegaMembershipOracle... otherOracles) {
    +        return newDynamicParallelOmegaOracle(Lists.asList(firstOracle, otherOracles));
    +    }
    +
    +    /**
    +     * Creates a {@link DynamicParallelOmegaOracleBuilder} using the provided collection of membership oracles. The
    +     * resulting parallel oracle will always use a {@link PoolPolicy#FIXED} pool policy and spawn a separate thread for
    +     * each of the provided oracles (so that the oracles do not need to care about synchronization if they don't share
    +     * state).
    +     *
    +     * @param oracles
    +     *         the oracle instances to distribute the queries to
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return the preconfigured oracle builder
    +     */
    +    public static  DynamicParallelOmegaOracleBuilder newDynamicParallelOmegaOracle(Collection> oracles) {
    +        return new DynamicParallelOmegaOracleBuilder<>(oracles);
    +    }
    +
    +    /**
    +     * Creates a {@link StaticParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that
    +     * the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific membership oracle instances
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  StaticParallelOracleBuilder> newStaticParallelOracle(SUL sul) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        return new StaticParallelOracleBuilder<>(toSupplier(sul));
    +    }
    +
    +    /**
    +     * Creates a {@link StaticParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that
    +     * the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific membership oracle instances
    +     * @param undefinedInput
    +     *         the input symbol used for responding to inputs that are not {@link StateLocalInputSUL#currentlyEnabledInputs()
    +     *         enabled}.
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  StaticParallelOracleBuilder> newStaticParallelOracle(StateLocalInputSUL sul,
    +                                                                                         O undefinedInput) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        return new StaticParallelOracleBuilder<>(toSupplier(sul, undefinedInput));
    +    }
    +
    +    /**
    +     * Creates a {@link StaticParallelOracleBuilder} using the provided supplier. Uses the further specified {@link
    +     * StaticParallelOracleBuilder#withPoolPolicy(PoolPolicy)} and {@link StaticParallelOracleBuilder#withNumInstances(int)}}
    +     * (or its defaults) to determine the thread pool.
          *
          * @param oracleSupplier
          *         the supplier for spawning new thread-specific membership oracle instances
    @@ -174,4 +345,97 @@ public static  StaticParallelOracleBuilder newStaticParallelOracle(M
         public static  StaticParallelOracleBuilder newStaticParallelOracle(Collection> oracles) {
             return new StaticParallelOracleBuilder<>(oracles);
         }
    +
    +    /**
    +     * Creates a {@link StaticParallelOmegaOracleBuilder} using the provided {@code sul} as a supplier. This requires
    +     * that the sul is {@link SUL#canFork() forkable}.
    +     *
    +     * @param sul
    +     *         the sul instance for spawning new thread-specific omega membership oracle instances
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  StaticParallelOmegaOracleBuilder> newStaticParallelOmegaOracle(ObservableSUL sul) {
    +        Preconditions.checkArgument(sul.canFork(), FORKABLE_SUL_ERR);
    +        // instantiate inner supplier to resolve generics
    +        return new StaticParallelOmegaOracleBuilder<>(toSupplier(sul)::get);
    +    }
    +
    +    /**
    +     * Creates a {@link StaticParallelOmegaOracleBuilder} using the provided supplier.
    +     *
    +     * @param oracleSupplier
    +     *         the supplier for spawning new thread-specific membership oracle instances
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    public static  StaticParallelOmegaOracleBuilder newStaticParallelOmegaOracle(Supplier> oracleSupplier) {
    +        return new StaticParallelOmegaOracleBuilder<>(oracleSupplier);
    +    }
    +
    +    /**
    +     * Convenience method for {@link #newStaticParallelOmegaOracle(Collection)}.
    +     *
    +     * @param firstOracle
    +     *         the first (mandatory) oracle
    +     * @param otherOracles
    +     *         further (optional) oracles to be used by other threads
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return a preconfigured oracle builder
    +     */
    +    @SafeVarargs
    +    public static  StaticParallelOmegaOracleBuilder newStaticParallelOmegaOracle(OmegaMembershipOracle firstOracle,
    +                                                                                                   OmegaMembershipOracle... otherOracles) {
    +        return newStaticParallelOmegaOracle(Lists.asList(firstOracle, otherOracles));
    +    }
    +
    +    /**
    +     * Creates a {@link StaticParallelOmegaOracleBuilder} using the provided collection of membership oracles. The
    +     * resulting parallel oracle will always use a {@link PoolPolicy#FIXED} pool policy and spawn a separate thread for
    +     * each of the provided oracles (so that the oracles do not need to care about synchronization if they don't share
    +     * state).
    +     *
    +     * @param oracles
    +     *         the oracle instances to distribute the queries to
    +     * @param 
    +     *         oracle state type
    +     * @param 
    +     *         input symbol type
    +     * @param 
    +     *         output domain type
    +     *
    +     * @return the preconfigured oracle builder
    +     */
    +    public static  StaticParallelOmegaOracleBuilder newStaticParallelOmegaOracle(Collection> oracles) {
    +        return new StaticParallelOmegaOracleBuilder<>(oracles);
    +    }
    +
    +    private static  Supplier> toSupplier(SUL sul) {
    +        return () -> new SULOracle<>(sul.fork());
    +    }
    +
    +    private static  Supplier> toSupplier(StateLocalInputSUL sul,
    +                                                                              O undefinedSymbol) {
    +        return () -> new StateLocalInputSULOracle<>(sul.fork(), undefinedSymbol);
    +    }
    +
    +    private static  Supplier>> toSupplier(ObservableSUL sul) {
    +        return () -> AbstractSULOmegaOracle.newOracle(sul.fork());
    +    }
     }
    diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracle.java
    new file mode 100644
    index 0000000000..95b74efe61
    --- /dev/null
    +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracle.java
    @@ -0,0 +1,60 @@
    +/* Copyright (C) 2013-2020 TU Dortmund
    + * This file is part of LearnLib, http://www.learnlib.de/.
    + *
    + * 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 de.learnlib.oracle.parallelism;
    +
    +import java.util.Collection;
    +
    +import de.learnlib.api.oracle.MembershipOracle;
    +import de.learnlib.api.oracle.OmegaMembershipOracle;
    +import de.learnlib.api.oracle.parallelism.ParallelOmegaOracle;
    +import de.learnlib.api.query.OmegaQuery;
    +import net.automatalib.words.Word;
    +import org.checkerframework.checker.index.qual.NonNegative;
    +
    +/**
    + * A specialized {@link AbstractStaticBatchProcessor} for {@link OmegaMembershipOracle}s that implements {@link
    + * ParallelOmegaOracle}.
    + *
    + * @param 
    + *         input symbol type
    + * @param 
    + *         output domain type
    + */
    +public class StaticParallelOmegaOracle
    +        extends AbstractStaticBatchProcessor, OmegaMembershipOracle>
    +        implements ParallelOmegaOracle {
    +
    +    public StaticParallelOmegaOracle(Collection> oracles,
    +                                     @NonNegative int minBatchSize,
    +                                     PoolPolicy policy) {
    +        super(oracles, minBatchSize, policy);
    +    }
    +
    +    @Override
    +    public void processQueries(Collection> omegaQueries) {
    +        processBatch(omegaQueries);
    +    }
    +
    +    @Override
    +    public MembershipOracle getMembershipOracle() {
    +        return getProcessor().getMembershipOracle();
    +    }
    +
    +    @Override
    +    public boolean isSameState(Word w1, S s1, Word w2, S s2) {
    +        return getProcessor().isSameState(w1, s1, w2, s2);
    +    }
    +}
    diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleBuilder.java
    new file mode 100644
    index 0000000000..8bd0f6b024
    --- /dev/null
    +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleBuilder.java
    @@ -0,0 +1,50 @@
    +/* Copyright (C) 2013-2020 TU Dortmund
    + * This file is part of LearnLib, http://www.learnlib.de/.
    + *
    + * 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 de.learnlib.oracle.parallelism;
    +
    +import java.util.Collection;
    +import java.util.function.Supplier;
    +
    +import de.learnlib.api.oracle.OmegaMembershipOracle;
    +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy;
    +import de.learnlib.api.query.OmegaQuery;
    +
    +/**
    + * A specialized {@link AbstractStaticBatchProcessorBuilder} for {@link OmegaMembershipOracle}s.
    + *
    + * @param 
    + *         input symbol type
    + * @param 
    + *         output domain type
    + */
    +public class StaticParallelOmegaOracleBuilder
    +        extends AbstractStaticBatchProcessorBuilder, OmegaMembershipOracle, StaticParallelOmegaOracle> {
    +
    +    public StaticParallelOmegaOracleBuilder(Supplier> oracleSupplier) {
    +        super(oracleSupplier);
    +    }
    +
    +    public StaticParallelOmegaOracleBuilder(Collection> oracles) {
    +        super(oracles);
    +    }
    +
    +    @Override
    +    protected StaticParallelOmegaOracle buildOracle(Collection> oracleInstances,
    +                                                             int minBatchSize,
    +                                                             PoolPolicy poolPolicy) {
    +        return new StaticParallelOmegaOracle<>(oracleInstances, minBatchSize, poolPolicy);
    +    }
    +}
    diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracle.java
    index c73db9534f..ebbfd8373b 100644
    --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracle.java
    +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracle.java
    @@ -15,157 +15,33 @@
      */
     package de.learnlib.oracle.parallelism;
     
    -import java.util.ArrayList;
     import java.util.Collection;
    -import java.util.Iterator;
    -import java.util.List;
    -import java.util.concurrent.ExecutionException;
    -import java.util.concurrent.ExecutorService;
    -import java.util.concurrent.Executors;
    -import java.util.concurrent.Future;
     
    -import com.google.common.base.Throwables;
     import de.learnlib.api.oracle.MembershipOracle;
    +import de.learnlib.api.oracle.parallelism.ParallelOracle;
     import de.learnlib.api.query.Query;
    -import de.learnlib.setting.LearnLibProperty;
    -import de.learnlib.setting.LearnLibSettings;
     import org.checkerframework.checker.index.qual.NonNegative;
     
     /**
    - * A membership oracle that statically distributes a set of queries among several threads.
    - * 

    - * An incoming set of queries is divided into a given number of batches, such that the sizes of all batches differ by at - * most one. This keeps the required synchronization effort low, but if some batches are "harder" (for whatever reason) - * than others, the load can be very unbalanced. + * A specialized {@link AbstractStaticBatchProcessor} for {@link MembershipOracle}s that implements {@link + * ParallelOracle}. * * @param * input symbol type * @param * output domain type - * - * @author Malte Isberner */ -public class StaticParallelOracle implements ParallelOracle { - - private static final int DEFAULT_MIN_BATCH_SIZE = 10; - public static final int MIN_BATCH_SIZE; - public static final int NUM_INSTANCES; - public static final PoolPolicy POOL_POLICY; - - static { - LearnLibSettings settings = LearnLibSettings.getInstance(); - - int numCores = Runtime.getRuntime().availableProcessors(); - - MIN_BATCH_SIZE = settings.getInt(LearnLibProperty.PARALLEL_BATCH_SIZE_STATIC, DEFAULT_MIN_BATCH_SIZE); - NUM_INSTANCES = settings.getInt(LearnLibProperty.PARALLEL_POOL_SIZE, numCores); - POOL_POLICY = settings.getEnumValue(LearnLibProperty.PARALLEL_POOL_SIZE, PoolPolicy.class, PoolPolicy.CACHED); - } - - - private final @NonNegative int minBatchSize; - private final MembershipOracle[] oracles; - private final ExecutorService executor; +public class StaticParallelOracle extends AbstractStaticBatchProcessor, MembershipOracle> + implements ParallelOracle { - @SuppressWarnings("unchecked") public StaticParallelOracle(Collection> oracles, @NonNegative int minBatchSize, PoolPolicy policy) { - - this.oracles = oracles.toArray(new MembershipOracle[0]); - - switch (policy) { - case FIXED: - this.executor = Executors.newFixedThreadPool(this.oracles.length - 1); - break; - case CACHED: - this.executor = Executors.newCachedThreadPool(); - break; - default: - throw new IllegalArgumentException("Illegal pool policy: " + policy); - } - this.minBatchSize = minBatchSize; + super(oracles, minBatchSize, policy); } @Override public void processQueries(Collection> queries) { - int num = queries.size(); - if (num <= 0) { - return; - } - - int numBatches = (num - minBatchSize) / minBatchSize + 1; - if (numBatches > oracles.length) { - numBatches = oracles.length; - } - - // One batch is always executed in the local thread. This saves the thread creation - // overhead for the common case where the batch size is quite small. - int externalBatches = numBatches - 1; - - if (externalBatches == 0) { - processQueriesLocally(queries); - return; - } - - // Calculate the number of full and non-full batches. The difference in size - // will never exceed one (cf. pidgeonhole principle) - int fullBatchSize = (num - 1) / numBatches + 1; - int nonFullBatches = fullBatchSize * numBatches - num; - - List> futures = new ArrayList<>(externalBatches); - - Iterator> queryIt = queries.iterator(); - - // Start the threads for the external batches - for (int i = 0; i < externalBatches; i++) { - int bs = fullBatchSize; - if (i < nonFullBatches) { - bs--; - } - List> batch = new ArrayList<>(bs); - for (int j = 0; j < bs; j++) { - batch.add(queryIt.next()); - } - - Runnable job = new StaticQueriesJob<>(batch, oracles[i + 1]); - Future future = executor.submit(job); - futures.add(future); - } - - // Finally, prepare and process the batch for the oracle executed in this thread. - List> localBatch = new ArrayList<>(fullBatchSize); - for (int j = 0; j < fullBatchSize; j++) { - localBatch.add(queryIt.next()); - } - - processQueriesLocally(localBatch); - - try { - for (Future f : futures) { - f.get(); - } - } catch (ExecutionException ex) { - Throwables.throwIfUnchecked(ex.getCause()); - throw new AssertionError("Runnable must not throw checked exceptions", ex); - } catch (InterruptedException ex) { - Thread.interrupted(); - throw new ParallelOracleInterruptedException(ex); - } - } - - private void processQueriesLocally(Collection> localBatch) { - oracles[0].processQueries(localBatch); + processBatch(queries); } - - @Override - public void shutdown() { - executor.shutdown(); - } - - @Override - public void shutdownNow() { - executor.shutdownNow(); - } - } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracleBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracleBuilder.java index 19ecc80f77..abd19dd1b7 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracleBuilder.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelOracleBuilder.java @@ -15,75 +15,36 @@ */ package de.learnlib.oracle.parallelism; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.function.Supplier; -import com.google.common.base.Preconditions; import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.oracle.parallelism.ParallelOracle.PoolPolicy; -import org.checkerframework.checker.index.qual.NonNegative; -import org.checkerframework.checker.nullness.qual.Nullable; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.Query; /** - * A builder for a {@link StaticParallelOracle}. + * A specialized {@link AbstractStaticBatchProcessorBuilder} for {@link MembershipOracle}s. * * @param * input symbol type * @param - * output type - * - * @author Malte Isberner + * output domain type */ -public class StaticParallelOracleBuilder { - - private final @Nullable Collection> oracles; - private final @Nullable Supplier> oracleSupplier; - private @NonNegative int minBatchSize = StaticParallelOracle.MIN_BATCH_SIZE; - private @NonNegative int numInstances = StaticParallelOracle.NUM_INSTANCES; - private PoolPolicy poolPolicy = StaticParallelOracle.POOL_POLICY; +public class StaticParallelOracleBuilder + extends AbstractStaticBatchProcessorBuilder, MembershipOracle, StaticParallelOracle> { public StaticParallelOracleBuilder(Collection> oracles) { - Preconditions.checkArgument(!oracles.isEmpty(), "No oracles specified"); - this.oracles = oracles; - this.oracleSupplier = null; + super(oracles); } public StaticParallelOracleBuilder(Supplier> oracleSupplier) { - this.oracles = null; - this.oracleSupplier = oracleSupplier; - } - - public StaticParallelOracleBuilder withMinBatchSize(@NonNegative int minBatchSize) { - this.minBatchSize = minBatchSize; - return this; - } - - public StaticParallelOracleBuilder withPoolPolicy(PoolPolicy policy) { - this.poolPolicy = policy; - return this; - } - - public StaticParallelOracleBuilder withNumInstances(@NonNegative int numInstances) { - this.numInstances = numInstances; - return this; + super(oracleSupplier); } - @SuppressWarnings("nullness") // the constructors guarantee that oracles and oracleSupplier are null exclusively - public StaticParallelOracle create() { - Collection> oracleInstances; - if (oracles != null) { - oracleInstances = oracles; - } else { - List> oracleList = new ArrayList<>(numInstances); - for (int i = 0; i < numInstances; i++) { - oracleList.add(oracleSupplier.get()); - } - oracleInstances = oracleList; - } - + @Override + protected StaticParallelOracle buildOracle(Collection> oracleInstances, + int minBatchSize, + PoolPolicy poolPolicy) { return new StaticParallelOracle<>(oracleInstances, minBatchSize, poolPolicy); } - } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticQueriesJob.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticQueriesJob.java index 57a2b2962b..a53e9b5ed5 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticQueriesJob.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticQueriesJob.java @@ -17,31 +17,28 @@ import java.util.Collection; -import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.api.query.Query; +import de.learnlib.api.oracle.parallelism.BatchProcessor; /** - * A queries job that maintains a fixed reference to a membership oracle, executes queries using this oracle regardless - * of the executing thread. + * A queries job that maintains a fixed reference to a {@link BatchProcessor}, executes queries using this oracle + * regardless of the executing thread. * - * @param - * input symbol type - * @param - * output type + * @param + * query type * * @author Malte Isberner */ -final class StaticQueriesJob extends AbstractQueriesJob { +final class StaticQueriesJob extends AbstractQueriesJob { - private final MembershipOracle oracle; + private final BatchProcessor oracle; - StaticQueriesJob(Collection> queries, MembershipOracle oracle) { + StaticQueriesJob(Collection queries, BatchProcessor oracle) { super(queries); this.oracle = oracle; } @Override - protected MembershipOracle getOracle() { + protected BatchProcessor getOracle() { return oracle; } diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOmegaOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOmegaOracleTest.java new file mode 100644 index 0000000000..d7849fe154 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOmegaOracleTest.java @@ -0,0 +1,159 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import de.learnlib.api.ObservableSUL; +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.OmegaMembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOmegaOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.OmegaQuery; +import net.automatalib.words.Word; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public abstract class AbstractDynamicParallelOmegaOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testEmpty(PoolPolicy poolPolicy) { + ParallelOmegaOracle oracle = getBuilder().withPoolPolicy(poolPolicy).create(); + + try { + oracle.processQueries(Collections.emptyList()); + } finally { + oracle.shutdownNow(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testDistinctQueries(PoolPolicy poolPolicy) { + ParallelOmegaOracle oracle = + getBuilder().withBatchSize(1).withPoolSize(4).withPoolPolicy(poolPolicy).create(); + + try { + List> queries = createQueries(100); + + oracle.processQueries(queries); + + for (AnswerOnceQuery query : queries) { + Assert.assertTrue(query.answered.get()); + } + } finally { + oracle.shutdown(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, expectedExceptions = IllegalStateException.class) + public void testDuplicateQueries(PoolPolicy poolPolicy) { + ParallelOmegaOracle oracle = + getBuilder().withBatchSize(1).withPoolSize(4).withPoolPolicy(poolPolicy).create(); + try { + List> queries = new ArrayList<>(createQueries(100)); + queries.add(queries.get(0)); + + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + protected abstract DynamicParallelOmegaOracleBuilder getBuilder(); + + protected static List> createQueries(int numQueries) { + List> queries = new ArrayList<>(numQueries); + + for (int i = 0; i < numQueries; i++) { + queries.add(new AnswerOnceQuery<>()); + } + + return queries; + } + + static class NullSUL implements ObservableSUL { + + @Override + public void pre() {} + + @Override + public void post() {} + + @Override + public Void step(Void in) { + return null; + } + + @Override + public Integer getState() { + return 1; + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public ObservableSUL fork() { + return new NullSUL(); + } + } + + static class NullOracle implements OmegaMembershipOracle { + + @Override + public void processQueries(Collection> omegaQueries) { + for (OmegaQuery q : omegaQueries) { + q.answer(null, 0); + } + } + + @Override + public MembershipOracle getMembershipOracle() { + throw new OmegaException(); + } + + @Override + public boolean isSameState(Word w1, Void s1, Word w2, Void s2) { + throw new OmegaException(); + } + } + + static final class AnswerOnceQuery extends OmegaQuery { + + private final AtomicBoolean answered = new AtomicBoolean(false); + + AnswerOnceQuery() { + super(Word.epsilon(), Word.epsilon(), 1); + } + + @Override + public void answer(@Nullable D output, int periodicity) { + boolean wasAnswered = answered.getAndSet(true); + if (wasAnswered) { + throw new IllegalStateException("Query was already answered"); + } + } + } + +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOracleTest.java new file mode 100644 index 0000000000..a7aac7fb4b --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelOracleTest.java @@ -0,0 +1,151 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import de.learnlib.api.StateLocalInputSUL; +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.Query; +import net.automatalib.words.Word; +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public abstract class AbstractDynamicParallelOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testEmpty(PoolPolicy poolPolicy) { + ParallelOracle oracle = getBuilder().withBatchSize(3).withPoolPolicy(poolPolicy).create(); + + try { + oracle.processQueries(Collections.emptyList()); + } finally { + oracle.shutdownNow(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testDistinctQueries(PoolPolicy poolPolicy) { + ParallelOracle oracle = + getBuilder().withBatchSize(1).withPoolSize(4).withPoolPolicy(poolPolicy).create(); + + try { + List> queries = createQueries(100); + + oracle.processQueries(queries); + + for (AnswerOnceQuery query : queries) { + Assert.assertTrue(query.answered.get()); + } + } finally { + oracle.shutdown(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, expectedExceptions = IllegalStateException.class) + public void testDuplicateQueries(PoolPolicy poolPolicy) { + ParallelOracle oracle = getBuilder().withBatchSize(3).withPoolPolicy(poolPolicy).create(); + try { + List> queries = new ArrayList<>(createQueries(100)); + queries.add(queries.get(0)); + + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + protected abstract DynamicParallelOracleBuilder getBuilder(); + + protected static List> createQueries(int numQueries) { + List> queries = new ArrayList<>(numQueries); + + for (int i = 0; i < numQueries; i++) { + queries.add(new AnswerOnceQuery<>()); + } + + return queries; + } + + static class NullSUL implements StateLocalInputSUL { + + @Override + public Collection currentlyEnabledInputs() { + return Collections.emptyList(); + } + + @Override + public void pre() {} + + @Override + public void post() {} + + @Override + public Void step(Void in) { + return null; + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public StateLocalInputSUL fork() { + return new NullSUL(); + } + } + + static class NullOracle implements MembershipOracle { + + @Override + public void processQueries(Collection> queries) { + for (Query q : queries) { + q.answer(null); + } + } + } + + static final class AnswerOnceQuery extends Query { + + private final AtomicBoolean answered = new AtomicBoolean(false); + + @Override + public void answer(D output) { + boolean wasAnswered = answered.getAndSet(true); + if (wasAnswered) { + throw new IllegalStateException("Query was already answered"); + } + } + + @Override + public Word getPrefix() { + return Word.epsilon(); + } + + @Override + public Word getSuffix() { + return Word.epsilon(); + } + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOmegaOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOmegaOracleTest.java new file mode 100644 index 0000000000..698d977697 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOmegaOracleTest.java @@ -0,0 +1,272 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.api.ObservableSUL; +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.OmegaMembershipOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.OmegaQuery; +import de.learnlib.oracle.parallelism.Utils.Analysis; +import de.learnlib.oracle.parallelism.Utils.TestSULOutput; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; +import org.testng.Assert; +import org.testng.annotations.Test; + +public abstract class AbstractStaticParallelOmegaOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testZeroQueries(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + oracle.processQueries(Collections.emptyList()); + Analysis ana = analyze(Collections.emptyList()); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 0); + oracle.shutdownNow(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanMin(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + List> queries = createQueries(Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testMin(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + List> queries = createQueries(Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanTwoBatches(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + List> queries = createQueries(2 * Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanSixBatches(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + List> queries = createQueries(5 * Utils.MIN_BATCH_SIZE + Utils.MIN_BATCH_SIZE / 2); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 5); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testFullLoad(PoolPolicy policy) { + StaticParallelOmegaOracle oracle = getOracle(policy); + List> queries = createQueries(2 * Utils.NUM_ORACLES * Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), Utils.NUM_ORACLES); + oracle.shutdown(); + } + + protected abstract StaticParallelOmegaOracleBuilder getBuilder(); + + protected abstract TestOutput extractTestOutput(D output); + + protected TestMembershipOracle[] getOracles() { + TestMembershipOracle[] oracles = new TestMembershipOracle[Utils.NUM_ORACLES]; + for (int i = 0; i < Utils.NUM_ORACLES; i++) { + oracles[i] = new TestMembershipOracle(i); + } + + return oracles; + } + + private StaticParallelOmegaOracle getOracle(PoolPolicy poolPolicy) { + return getBuilder().withMinBatchSize(Utils.MIN_BATCH_SIZE) + .withNumInstances(Utils.NUM_ORACLES) + .withPoolPolicy(poolPolicy) + .create(); + } + + protected int getMinQueryLength() { + return 0; + } + + private List> createQueries(int num) { + List> result = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + OmegaQuery qry = + new OmegaQuery<>(Utils.createWord(getMinQueryLength()), Utils.createWord(getMinQueryLength()), 1); + result.add(qry); + } + return result; + } + + private Analysis analyze(Collection> queries) { + List oracles = new ArrayList<>(); + Map> seqIds = new HashMap<>(); + Map incorrectAnswers = new HashMap<>(); + + for (OmegaQuery qry : queries) { + TestOutput out = extractTestOutput(qry.getOutput()); + Assert.assertNotNull(out); + int oracleId = out.oracleId; + List seqIdList = seqIds.get(oracleId); + if (seqIdList == null) { + oracles.add(oracleId); + seqIdList = new ArrayList<>(); + seqIds.put(oracleId, seqIdList); + incorrectAnswers.put(oracleId, 0); + } + + int seqId = out.batchSeqId; + seqIdList.add(seqId); + + if (!qry.asDefaultQuery().getInput().equals(out.input)) { + incorrectAnswers.put(oracleId, incorrectAnswers.get(oracleId) + 1); + } + } + + int minBatchSize = -1; + int maxBatchSize = -1; + for (List batch : seqIds.values()) { + if (minBatchSize == -1) { + maxBatchSize = batch.size(); + minBatchSize = maxBatchSize; + } else { + if (batch.size() < minBatchSize) { + minBatchSize = batch.size(); + } + if (batch.size() > maxBatchSize) { + maxBatchSize = batch.size(); + } + } + } + + return new Analysis(oracles, seqIds, incorrectAnswers, minBatchSize, maxBatchSize); + } + + static final class TestOutput { + + public final int oracleId; + public final int batchSeqId; + public final Word input; + + TestOutput(int oracleId, int batchSeqId, Word input) { + this.oracleId = oracleId; + this.batchSeqId = batchSeqId; + this.input = input; + } + } + + static final class TestMembershipOracle implements OmegaMembershipOracle { + + private final int oracleId; + + TestMembershipOracle(int oracleId) { + this.oracleId = oracleId; + } + + @Override + public void processQueries(Collection> queries) { + int batchSeqId = 0; + for (OmegaQuery qry : queries) { + qry.answer(new TestOutput(oracleId, batchSeqId++, qry.asDefaultQuery().getInput()), + qry.getPeriodicity()); + } + } + + @Override + public MembershipOracle getMembershipOracle() { + throw new OmegaException(); + } + + @Override + public boolean isSameState(Word w1, Integer s1, Word w2, Integer s2) { + throw new OmegaException(); + } + } + + static final class TestSUL implements ObservableSUL { + + private final AtomicInteger atomicInteger; + private final int oracleId; + private int batchSeqId; + + private final WordBuilder wb; + + TestSUL(AtomicInteger atomicInteger) { + this.atomicInteger = atomicInteger; + this.oracleId = atomicInteger.getAndIncrement(); + this.batchSeqId = -1; // so that our first query starts at 0 + + this.wb = new WordBuilder<>(); + } + + @Override + public void pre() { + batchSeqId++; + } + + @Override + public void post() { + this.wb.clear(); + } + + @Override + public TestSULOutput step(Integer in) { + this.wb.append(in); + return new TestSULOutput(oracleId, batchSeqId, wb.toWord()); + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public TestSUL fork() { + return new TestSUL(this.atomicInteger); + } + + @Override + public Integer getState() { + return 1; + } + } + +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOracleTest.java new file mode 100644 index 0000000000..42ecf7d0ec --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelOracleTest.java @@ -0,0 +1,271 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.api.StateLocalInputSUL; +import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.DefaultQuery; +import de.learnlib.api.query.Query; +import de.learnlib.oracle.parallelism.Utils.Analysis; +import de.learnlib.oracle.parallelism.Utils.TestSULOutput; +import net.automatalib.words.Word; +import net.automatalib.words.WordBuilder; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +public abstract class AbstractStaticParallelOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testZeroQueries(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + oracle.processQueries(Collections.emptyList()); + Analysis ana = analyze(Collections.emptyList()); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 0); + oracle.shutdownNow(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanMin(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + List> queries = createQueries(Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testMin(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + List> queries = createQueries(Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanTwoBatches(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + List> queries = createQueries(2 * Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanSixBatches(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + List> queries = createQueries(5 * Utils.MIN_BATCH_SIZE + Utils.MIN_BATCH_SIZE / 2); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 5); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testFullLoad(PoolPolicy policy) { + StaticParallelOracle oracle = getOracle(policy); + List> queries = createQueries(2 * Utils.NUM_ORACLES * Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), Utils.NUM_ORACLES); + oracle.shutdown(); + } + + protected abstract StaticParallelOracleBuilder getBuilder(); + + protected abstract TestOutput extractTestOutput(D output); + + protected TestMembershipOracle[] getOracles() { + TestMembershipOracle[] oracles = new TestMembershipOracle[Utils.NUM_ORACLES]; + for (int i = 0; i < Utils.NUM_ORACLES; i++) { + oracles[i] = new TestMembershipOracle(i); + } + + return oracles; + } + + private StaticParallelOracle getOracle(PoolPolicy poolPolicy) { + return getBuilder().withMinBatchSize(Utils.MIN_BATCH_SIZE) + .withNumInstances(Utils.NUM_ORACLES) + .withPoolPolicy(poolPolicy) + .create(); + } + + protected int getMinQueryLength() { + return 0; + } + + private List> createQueries(int num) { + List> result = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + DefaultQuery qry = + new DefaultQuery<>(Utils.createWord(getMinQueryLength()), Utils.createWord(getMinQueryLength())); + result.add(qry); + } + return result; + } + + private Analysis analyze(Collection> queries) { + List oracles = new ArrayList<>(); + Map> seqIds = new HashMap<>(); + Map incorrectAnswers = new HashMap<>(); + + for (DefaultQuery qry : queries) { + TestOutput out = extractTestOutput(qry.getOutput()); + Assert.assertNotNull(out); + int oracleId = out.oracleId; + List seqIdList = seqIds.get(oracleId); + if (seqIdList == null) { + oracles.add(oracleId); + seqIdList = new ArrayList<>(); + seqIds.put(oracleId, seqIdList); + incorrectAnswers.put(oracleId, 0); + } + + int seqId = out.batchSeqId; + seqIdList.add(seqId); + + if (!qry.getPrefix().equals(out.prefix) || !qry.getSuffix().equals(out.suffix)) { + incorrectAnswers.put(oracleId, incorrectAnswers.get(oracleId) + 1); + } + } + + int minBatchSize = -1; + int maxBatchSize = -1; + for (List batch : seqIds.values()) { + if (minBatchSize == -1) { + maxBatchSize = batch.size(); + minBatchSize = maxBatchSize; + } else { + if (batch.size() < minBatchSize) { + minBatchSize = batch.size(); + } + if (batch.size() > maxBatchSize) { + maxBatchSize = batch.size(); + } + } + } + + return new Analysis(oracles, seqIds, incorrectAnswers, minBatchSize, maxBatchSize); + } + + static final class TestOutput { + + public final int oracleId; + public final int batchSeqId; + public final Word prefix; + public final Word suffix; + + TestOutput(int oracleId, int batchSeqId, Word prefix, Word suffix) { + this.oracleId = oracleId; + this.batchSeqId = batchSeqId; + this.prefix = prefix; + this.suffix = suffix; + } + } + + static final class TestMembershipOracle implements MembershipOracle { + + private final int oracleId; + + TestMembershipOracle(int oracleId) { + this.oracleId = oracleId; + } + + @Override + public void processQueries(Collection> queries) { + int batchSeqId = 0; + for (Query qry : queries) { + qry.answer(new TestOutput(oracleId, batchSeqId++, qry.getPrefix(), qry.getSuffix())); + } + } + } + + static final class TestSUL implements StateLocalInputSUL { + + @SuppressWarnings("unchecked") + private static final Collection ALPHABET = Mockito.mock(Collection.class); + + static { + Mockito.doAnswer(invocation -> true).when(ALPHABET).contains(Mockito.any()); + } + + private final AtomicInteger atomicInteger; + private final int oracleId; + private int batchSeqId; + + private final WordBuilder wb; + + TestSUL(AtomicInteger atomicInteger) { + this.atomicInteger = atomicInteger; + this.oracleId = atomicInteger.getAndIncrement(); + this.batchSeqId = -1; // so that our first query starts at 0 + + this.wb = new WordBuilder<>(); + } + + @Override + public Collection currentlyEnabledInputs() { + return ALPHABET; + } + + @Override + public void pre() { + batchSeqId++; + } + + @Override + public void post() { + this.wb.clear(); + } + + @Override + public TestSULOutput step(Integer in) { + this.wb.append(in); + return new TestSULOutput(oracleId, batchSeqId, wb.toWord()); + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public TestSUL fork() { + return new TestSUL(this.atomicInteger); + } + } + +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelObservableSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelObservableSULTest.java new file mode 100644 index 0000000000..77ba9e8c83 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelObservableSULTest.java @@ -0,0 +1,26 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import net.automatalib.words.Word; + +public class DynamicParallelObservableSULTest extends AbstractDynamicParallelOmegaOracleTest> { + + @Override + protected DynamicParallelOmegaOracleBuilder> getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOmegaOracle(new NullSUL()); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleTest.java new file mode 100644 index 0000000000..09ccef738f --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaOracleTest.java @@ -0,0 +1,128 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import de.learnlib.api.oracle.parallelism.ParallelOmegaOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.api.query.OmegaQuery; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class DynamicParallelOmegaOracleTest extends AbstractDynamicParallelOmegaOracleTest { + + @Override + protected DynamicParallelOmegaOracleBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOmegaOracle(Arrays.asList(new NullOracle(), + new NullOracle(), + new NullOracle())); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadCreation(PoolPolicy poolPolicy) { + + final List> queries = createQueries(10); + final int expectedThreads = queries.size(); + + final CountDownLatch latch = new CountDownLatch(expectedThreads); + final NullOracle[] oracles = new NullOracle[expectedThreads]; + + for (int i = 0; i < expectedThreads; i++) { + oracles[i] = new NullOracle() { + + @Override + public void processQueries(Collection> queries) { + try { + latch.countDown(); + latch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + super.processQueries(queries); + } + }; + } + + final ParallelOmegaOracle oracle = + ParallelOracleBuilders.newDynamicParallelOmegaOracle(oracles[0], + Arrays.copyOfRange(oracles, 1, oracles.length)) + .withBatchSize(1) + .withPoolSize(oracles.length) + .withPoolPolicy(poolPolicy) + .create(); + + try { + // this method only returns, if 'expectedThreads' threads are spawned, which all decrease the shared latch + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadScheduling(PoolPolicy poolPolicy) { + + final List> queries = createQueries(10); + final CountDownLatch latch = new CountDownLatch(queries.size() - 1); + + final NullOracle awaitingOracle = new NullOracle() { + + @Override + public void processQueries(Collection> queries) { + try { + latch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + super.processQueries(queries); + } + }; + + final NullOracle countDownOracle = new NullOracle() { + + @Override + public void processQueries(Collection> queries) { + latch.countDown(); + super.processQueries(queries); + } + }; + + final ParallelOmegaOracle oracle = + ParallelOracleBuilders.newDynamicParallelOmegaOracle(awaitingOracle, countDownOracle) + .withPoolSize(2) + .withPoolPolicy(poolPolicy) + .create(); + + try { + // this method only returns, if the countDownOracle was scheduled 9 times to unblock the awaitingOracle + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + @Test + public void testSingleMethods() { + final ParallelOmegaOracle oracle = getBuilder().create(); + + Assert.assertThrows(OmegaException.class, oracle::getMembershipOracle); + Assert.assertThrows(OmegaException.class, () -> oracle.isSameState(null, null, null, null)); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaSupplierTest.java new file mode 100644 index 0000000000..e9e5b4cd16 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOmegaSupplierTest.java @@ -0,0 +1,24 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +public class DynamicParallelOmegaSupplierTest extends AbstractDynamicParallelOmegaOracleTest { + + @Override + protected DynamicParallelOmegaOracleBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOmegaOracle(NullOracle::new); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOracleTest.java index 06bfb1871d..3210b1dda2 100644 --- a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOracleTest.java +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelOracleTest.java @@ -15,103 +15,29 @@ */ package de.learnlib.oracle.parallelism; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; -import de.learnlib.api.oracle.MembershipOracle; +import de.learnlib.api.oracle.parallelism.ParallelOracle; +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; import de.learnlib.api.query.Query; -import de.learnlib.oracle.parallelism.DynamicParallelOracleBuilder.StaticOracleProvider; -import de.learnlib.oracle.parallelism.ParallelOracle.PoolPolicy; -import net.automatalib.words.Word; -import org.testng.Assert; import org.testng.annotations.Test; -@Test -public class DynamicParallelOracleTest { - - @Test - public void testDistinctQueries() { - ParallelOracle oracle = ParallelOracleBuilders.newDynamicParallelOracle(new NullOracle()) - .withBatchSize(1) - .withPoolSize(4) - .withPoolPolicy(PoolPolicy.CACHED) - .create(); - - try { - List queries = createQueries(100); - - oracle.processQueries(queries); - - for (AnswerOnceQuery query : queries) { - Assert.assertTrue(query.answered.get()); - } - } finally { - oracle.shutdown(); - } - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testDuplicateQueries() { - ParallelOracle oracle = ParallelOracleBuilders.newDynamicParallelOracle(new NullOracle()) - .withBatchSize(1) - .withPoolSize(4) - .withPoolPolicy(PoolPolicy.CACHED) - .create(); - try { - List queries = new ArrayList<>(createQueries(100)); - queries.add(queries.get(0)); - - oracle.processQueries(queries); - } finally { - oracle.shutdown(); - } - } - - @Test(timeOut = 2000) - public void testCachedThreadCreation() { - // Explicitly use the supplier version, to test the cached pool creation - testThreadCreation(oracles -> ParallelOracleBuilders.newDynamicParallelOracle(new StaticOracleProvider<>(oracles)) - .withBatchSize(1) - .withPoolSize(oracles.size()) - .withPoolPolicy(PoolPolicy.CACHED) - .create()); - } - - @Test(timeOut = 2000) - public void testFixedThreadCreation() { - // Test the fixed pool creation - testThreadCreation(oracles -> ParallelOracleBuilders.newDynamicParallelOracle(oracles) - .withBatchSize(1) - .create()); - } - - @Test(timeOut = 2000) - public void testCachedThreadScheduling() { - // Explicitly use the supplier version, to test the cached pool creation - testThreadScheduling(oracles -> ParallelOracleBuilders.newDynamicParallelOracle(new StaticOracleProvider<>(oracles)) - .withBatchSize(1) - .withPoolSize(2) - .withPoolPolicy(PoolPolicy.CACHED) - .create()); +public class DynamicParallelOracleTest extends AbstractDynamicParallelOracleTest { + @Override + protected DynamicParallelOracleBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOracle(Arrays.asList(new NullOracle(), + new NullOracle(), + new NullOracle())); } - @Test(timeOut = 2000) - public void testFixedThreadScheduling() { - // Test the fixed pool creation - testThreadScheduling(oracles -> ParallelOracleBuilders.newDynamicParallelOracle(oracles) - .withBatchSize(1) - .create()); - } - - private void testThreadCreation(Function, ParallelOracle> parallelOracleFunction) { + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadCreation(PoolPolicy poolPolicy) { - final List queries = createQueries(10); + final List> queries = createQueries(10); final int expectedThreads = queries.size(); final CountDownLatch latch = new CountDownLatch(expectedThreads); @@ -133,7 +59,15 @@ public void processQueries(Collection> queries) { }; } - final ParallelOracle oracle = parallelOracleFunction.apply(Arrays.asList(oracles)); + final ParallelOracle oracle = ParallelOracleBuilders.newDynamicParallelOracle(oracles[0], + Arrays.copyOfRange( + oracles, + 1, + oracles.length)) + .withBatchSize(1) + .withPoolSize(oracles.length) + .withPoolPolicy(poolPolicy) + .create(); try { // this method only returns, if 'expectedThreads' threads are spawned, which all decrease the shared latch @@ -143,9 +77,10 @@ public void processQueries(Collection> queries) { } } - private void testThreadScheduling(Function, ParallelOracle> parallelOracleFunction) { + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadScheduling(PoolPolicy poolPolicy) { - final List queries = createQueries(10); + final List> queries = createQueries(10); final CountDownLatch latch = new CountDownLatch(queries.size() - 1); final NullOracle awaitingOracle = new NullOracle() { @@ -171,7 +106,10 @@ public void processQueries(Collection> queries) { }; final ParallelOracle oracle = - parallelOracleFunction.apply(Arrays.asList(awaitingOracle, countDownOracle)); + ParallelOracleBuilders.newDynamicParallelOracle(awaitingOracle, countDownOracle) + .withPoolSize(2) + .withPoolPolicy(poolPolicy) + .create(); try { // this method only returns, if the countDownOracle was scheduled 9 times to unblock the awaitingOracle @@ -180,49 +118,4 @@ public void processQueries(Collection> queries) { oracle.shutdown(); } } - - private static List createQueries(int numQueries) { - List queries = new ArrayList<>(numQueries); - - for (int i = 0; i < numQueries; i++) { - queries.add(new AnswerOnceQuery()); - } - - return queries; - } - - private static class NullOracle implements MembershipOracle { - - @Override - public void processQueries(Collection> queries) { - for (Query q : queries) { - q.answer(null); - } - } - } - - private static final class AnswerOnceQuery extends Query { - - private final AtomicBoolean answered = new AtomicBoolean(false); - - @Override - public void answer(Void output) { - boolean wasAnswered = answered.getAndSet(true); - if (wasAnswered) { - throw new IllegalStateException("Query was already answered"); - } - } - - @Override - public Word getPrefix() { - return Word.epsilon(); - } - - @Override - public Word getSuffix() { - return Word.epsilon(); - } - - } - } diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSLISULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSLISULTest.java new file mode 100644 index 0000000000..cc7aa3ffeb --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSLISULTest.java @@ -0,0 +1,26 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import net.automatalib.words.Word; + +public class DynamicParallelSLISULTest extends AbstractDynamicParallelOracleTest> { + + @Override + protected DynamicParallelOracleBuilder> getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOracle(new NullSUL(), null); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSULTest.java new file mode 100644 index 0000000000..2f0e7d351d --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSULTest.java @@ -0,0 +1,26 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import net.automatalib.words.Word; + +public class DynamicParallelSULTest extends AbstractDynamicParallelOracleTest> { + + @Override + protected DynamicParallelOracleBuilder> getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOracle(new NullSUL()); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSupplierTest.java new file mode 100644 index 0000000000..106c968609 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelSupplierTest.java @@ -0,0 +1,24 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +public class DynamicParallelSupplierTest extends AbstractDynamicParallelOracleTest { + + @Override + protected DynamicParallelOracleBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelOracle(NullOracle::new); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/OmegaException.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/OmegaException.java new file mode 100644 index 0000000000..7604001347 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/OmegaException.java @@ -0,0 +1,18 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +class OmegaException extends RuntimeException {} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java new file mode 100644 index 0000000000..daafcd0b1b --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java @@ -0,0 +1,47 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.oracle.parallelism.Utils.TestSULOutput; +import net.automatalib.words.Word; + +public class StaticParallelObservableSULTest extends AbstractStaticParallelOmegaOracleTest> { + + @Override + protected StaticParallelOmegaOracleBuilder> getBuilder() { + // since we fork our initial SUL, start at -1 + return ParallelOracleBuilders.newStaticParallelOmegaOracle(new TestSUL(new AtomicInteger(-1))); + } + + @Override + protected TestOutput extractTestOutput(Word output) { + assert output.size() > 0; + + final TestSULOutput lastSym = output.lastSymbol(); + final int oracleId = lastSym.oracleId; + final int batchSeqId = lastSym.batchSeqId; + final Word word = lastSym.word; + + return new TestOutput(oracleId, batchSeqId, word); + } + + @Override + protected int getMinQueryLength() { + return 1; + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleTest.java new file mode 100644 index 0000000000..c77cfb3471 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaOracleTest.java @@ -0,0 +1,46 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.Arrays; + +import de.learnlib.api.oracle.parallelism.ParallelOmegaOracle; +import de.learnlib.oracle.parallelism.AbstractStaticParallelOmegaOracleTest.TestOutput; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StaticParallelOmegaOracleTest extends AbstractStaticParallelOmegaOracleTest { + + @Override + protected StaticParallelOmegaOracleBuilder getBuilder() { + TestMembershipOracle[] oracles = getOracles(); + return ParallelOracleBuilders.newStaticParallelOmegaOracle(oracles[0], + Arrays.copyOfRange(oracles, 1, oracles.length)); + } + + @Override + protected TestOutput extractTestOutput(TestOutput output) { + return output; + } + + @Test + public void testSingleMethods() { + final ParallelOmegaOracle oracle = getBuilder().create(); + + Assert.assertThrows(OmegaException.class, oracle::getMembershipOracle); + Assert.assertThrows(OmegaException.class, () -> oracle.isSameState(null, null, null, null)); + } +} \ No newline at end of file diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaSupplierTest.java new file mode 100644 index 0000000000..0b725c736b --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOmegaSupplierTest.java @@ -0,0 +1,33 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import de.learnlib.oracle.parallelism.AbstractDynamicBatchProcessorBuilder.StaticOracleProvider; +import de.learnlib.oracle.parallelism.AbstractStaticParallelOmegaOracleTest.TestOutput; + +public class StaticParallelOmegaSupplierTest extends AbstractStaticParallelOmegaOracleTest { + + @Override + protected StaticParallelOmegaOracleBuilder getBuilder() { + TestMembershipOracle[] oracles = getOracles(); + return ParallelOracleBuilders.newStaticParallelOmegaOracle(new StaticOracleProvider<>(oracles)); + } + + @Override + protected TestOutput extractTestOutput(TestOutput output) { + return output; + } +} \ No newline at end of file diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOracleTest.java index bd9755e112..4548c89cb4 100644 --- a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOracleTest.java +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelOracleTest.java @@ -15,243 +15,21 @@ */ package de.learnlib.oracle.parallelism; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.Arrays; -import de.learnlib.api.oracle.MembershipOracle; -import de.learnlib.api.query.DefaultQuery; -import de.learnlib.api.query.Query; -import de.learnlib.oracle.parallelism.DynamicParallelOracleBuilder.StaticOracleProvider; -import de.learnlib.oracle.parallelism.ParallelOracle.PoolPolicy; -import net.automatalib.words.Word; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Factory; -import org.testng.annotations.Test; +import de.learnlib.oracle.parallelism.AbstractStaticParallelOracleTest.TestOutput; -public class StaticParallelOracleTest { +public class StaticParallelOracleTest extends AbstractStaticParallelOracleTest { - public static final int NUM_ORACLES = 10; - public static final int MIN_BATCH_SIZE = 20; - public static final int MAX_WORD_LEN = 30; - - private static final Random RANDOM = new Random(); - private final StaticParallelOracle parallelOracle; - - @Factory(dataProvider = "staticOracles") - public StaticParallelOracleTest(StaticParallelOracle parallelOracle) { - this.parallelOracle = parallelOracle; - } - - @DataProvider(name = "staticOracles") - public static Object[][] createStaticParallelOracles() { - List oracles = new ArrayList<>(NUM_ORACLES); - for (int i = 0; i < NUM_ORACLES; i++) { - oracles.add(new TestMembershipOracle(i)); - } - - final Object[][] result = new Object[2][1]; - - result[0][0] = - ParallelOracleBuilders.newStaticParallelOracle(oracles).withMinBatchSize(MIN_BATCH_SIZE).create(); - result[1][0] = ParallelOracleBuilders.newStaticParallelOracle(new StaticOracleProvider<>(oracles)) - .withMinBatchSize(MIN_BATCH_SIZE) - .withNumInstances(NUM_ORACLES) - .withPoolPolicy(PoolPolicy.CACHED) - .create(); - - return result; - } - - @AfterClass - public void tearDown() { - parallelOracle.shutdown(); - } - - @Test - public void testLessThanMin() { - List> queries = createQueries(MIN_BATCH_SIZE - 1); - parallelOracle.processQueries(queries); - Analysis ana = analyze(queries); - sanityCheck(ana); - Assert.assertEquals(ana.involvedOracles.size(), 1); - } - - private static List> createQueries(int num) { - List> result = new ArrayList<>(num); - for (int i = 0; i < num; i++) { - DefaultQuery qry = new DefaultQuery<>(createWord(), createWord()); - result.add(qry); - } - return result; - } - - private static Analysis analyze(Collection> queries) { - List oracles = new ArrayList<>(); - Map> seqIds = new HashMap<>(); - Map incorrectAnswers = new HashMap<>(); - - for (DefaultQuery qry : queries) { - TestOutput out = qry.getOutput(); - Assert.assertNotNull(out); - int oracleId = out.oracleId; - List seqIdList = seqIds.get(oracleId); - if (seqIdList == null) { - oracles.add(oracleId); - seqIdList = new ArrayList<>(); - seqIds.put(oracleId, seqIdList); - incorrectAnswers.put(oracleId, 0); - } - - int seqId = out.batchSeqId; - seqIdList.add(seqId); - - if (!qry.getPrefix().equals(out.prefix) || !qry.getSuffix().equals(out.suffix)) { - incorrectAnswers.put(oracleId, incorrectAnswers.get(oracleId) + 1); - } - } - - int minBatchSize = -1; - int maxBatchSize = -1; - for (List batch : seqIds.values()) { - if (minBatchSize == -1) { - maxBatchSize = batch.size(); - minBatchSize = maxBatchSize; - } else { - if (batch.size() < minBatchSize) { - minBatchSize = batch.size(); - } - if (batch.size() > maxBatchSize) { - maxBatchSize = batch.size(); - } - } - } - - return new Analysis(oracles, seqIds, incorrectAnswers, minBatchSize, maxBatchSize); - } - - private static void sanityCheck(Analysis analysis) { - for (Integer oracleId : analysis.involvedOracles) { - List seqIds = analysis.sequenceIds.get(oracleId); - Assert.assertNotNull(seqIds); - boolean[] ids = new boolean[seqIds.size()]; - for (Integer seqId : seqIds) { - Assert.assertNotNull(seqId); - Assert.assertTrue(seqId >= 0); - Assert.assertTrue(seqId < ids.length); - Assert.assertFalse(ids[seqId]); - ids[seqId] = true; - } - Integer incAnswers = analysis.incorrectAnswers.get(oracleId); - Assert.assertEquals(incAnswers.intValue(), 0); - } - Assert.assertTrue((analysis.maxBatchSize - analysis.minBatchSize) <= 1); - - Assert.assertTrue(analysis.involvedOracles.size() <= 1 || analysis.minBatchSize >= MIN_BATCH_SIZE); + @Override + protected StaticParallelOracleBuilder getBuilder() { + TestMembershipOracle[] oracles = getOracles(); + return ParallelOracleBuilders.newStaticParallelOracle(oracles[0], + Arrays.copyOfRange(oracles, 1, oracles.length)); } - private static Word createWord() { - int length = RANDOM.nextInt(MAX_WORD_LEN); - Integer[] ints = new Integer[length]; - for (int i = 0; i < ints.length; i++) { - ints[i] = RANDOM.nextInt(); - } - return Word.fromSymbols(ints); + @Override + protected TestOutput extractTestOutput(TestOutput output) { + return output; } - - @Test - public void testMin() { - List> queries = createQueries(MIN_BATCH_SIZE); - parallelOracle.processQueries(queries); - Analysis ana = analyze(queries); - sanityCheck(ana); - Assert.assertEquals(ana.involvedOracles.size(), 1); - } - - @Test - public void testLessThanTwoBatches() { - List> queries = createQueries(2 * MIN_BATCH_SIZE - 1); - parallelOracle.processQueries(queries); - Analysis ana = analyze(queries); - sanityCheck(ana); - Assert.assertEquals(ana.involvedOracles.size(), 1); - } - - @Test - public void testLessThanSixBatches() { - List> queries = createQueries(5 * MIN_BATCH_SIZE + MIN_BATCH_SIZE / 2); - parallelOracle.processQueries(queries); - Analysis ana = analyze(queries); - sanityCheck(ana); - Assert.assertEquals(ana.involvedOracles.size(), 5); - } - - @Test - public void testFullLoad() { - List> queries = createQueries(2 * NUM_ORACLES * MIN_BATCH_SIZE); - parallelOracle.processQueries(queries); - Analysis ana = analyze(queries); - sanityCheck(ana); - Assert.assertEquals(ana.involvedOracles.size(), NUM_ORACLES); - } - - private static final class TestOutput { - - private final int oracleId; - private final int batchSeqId; - private final Word prefix; - private final Word suffix; - - TestOutput(int oracleId, int batchSeqId, Word prefix, Word suffix) { - this.oracleId = oracleId; - this.batchSeqId = batchSeqId; - this.prefix = prefix; - this.suffix = suffix; - } - } - - private static final class Analysis { - - private final List involvedOracles; - private final Map> sequenceIds; - private final Map incorrectAnswers; - private final int minBatchSize; - private final int maxBatchSize; - - Analysis(List involvedOracles, - Map> sequenceIds, - Map incorrectAnswers, - int minBatchSize, - int maxBatchSize) { - this.involvedOracles = involvedOracles; - this.sequenceIds = sequenceIds; - this.incorrectAnswers = incorrectAnswers; - this.minBatchSize = minBatchSize; - this.maxBatchSize = maxBatchSize; - } - } - - private static final class TestMembershipOracle implements MembershipOracle { - - private final int oracleId; - - TestMembershipOracle(int oracleId) { - this.oracleId = oracleId; - } - - @Override - public void processQueries(Collection> queries) { - int batchSeqId = 0; - for (Query qry : queries) { - qry.answer(new TestOutput(oracleId, batchSeqId++, qry.getPrefix(), qry.getSuffix())); - } - } - - } - -} +} \ No newline at end of file diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSLISULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSLISULTest.java new file mode 100644 index 0000000000..260096dfb4 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSLISULTest.java @@ -0,0 +1,40 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.oracle.parallelism.Utils.TestSULOutput; +import net.automatalib.words.Word; + +public class StaticParallelSLISULTest extends AbstractStaticParallelOracleTest> { + + @Override + protected StaticParallelOracleBuilder> getBuilder() { + // since we fork our initial SUL, start at -1 + return ParallelOracleBuilders.newStaticParallelOracle(new TestSUL(new AtomicInteger(-1)), null); + } + + @Override + protected TestOutput extractTestOutput(Word output) { + return Utils.extractSULOutput(output); + } + + @Override + protected int getMinQueryLength() { + return 1; + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSULTest.java new file mode 100644 index 0000000000..5c82d02b6b --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSULTest.java @@ -0,0 +1,40 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.oracle.parallelism.Utils.TestSULOutput; +import net.automatalib.words.Word; + +public class StaticParallelSULTest extends AbstractStaticParallelOracleTest> { + + @Override + protected StaticParallelOracleBuilder> getBuilder() { + // since we fork our initial SUL, start at -1 + return ParallelOracleBuilders.newStaticParallelOracle(new TestSUL(new AtomicInteger(-1))); + } + + @Override + protected TestOutput extractTestOutput(Word output) { + return Utils.extractSULOutput(output); + } + + @Override + protected int getMinQueryLength() { + return 1; + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSupplierTest.java new file mode 100644 index 0000000000..0236237933 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelSupplierTest.java @@ -0,0 +1,32 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import de.learnlib.oracle.parallelism.AbstractDynamicBatchProcessorBuilder.StaticOracleProvider; +import de.learnlib.oracle.parallelism.AbstractStaticParallelOracleTest.TestOutput; + +public class StaticParallelSupplierTest extends AbstractStaticParallelOracleTest { + + @Override + protected StaticParallelOracleBuilder getBuilder() { + return ParallelOracleBuilders.newStaticParallelOracle(new StaticOracleProvider<>(getOracles())); + } + + @Override + protected TestOutput extractTestOutput(TestOutput output) { + return output; + } +} \ No newline at end of file diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/Utils.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/Utils.java new file mode 100644 index 0000000000..3df347d379 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/Utils.java @@ -0,0 +1,119 @@ +/* Copyright (C) 2013-2020 TU Dortmund + * This file is part of LearnLib, http://www.learnlib.de/. + * + * 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 de.learnlib.oracle.parallelism; + +import java.util.List; +import java.util.Map; +import java.util.Random; + +import de.learnlib.api.oracle.parallelism.ThreadPool.PoolPolicy; +import de.learnlib.oracle.parallelism.AbstractStaticParallelOracleTest.TestOutput; +import net.automatalib.words.Word; +import org.testng.Assert; +import org.testng.annotations.DataProvider; + +final class Utils { + + static final int NUM_ORACLES = 10; + static final int MIN_BATCH_SIZE = 20; + static final int MAX_WORD_LEN = 30; + + static final Random RANDOM = new Random(); + + private Utils() {} + + @DataProvider(name = "policies") + static Object[][] createPolicies() { + return new Object[][] {new Object[] {PoolPolicy.CACHED}, new Object[] {PoolPolicy.FIXED}}; + } + + static Word createWord(int minLength) { + int length = Math.max(minLength, RANDOM.nextInt(MAX_WORD_LEN)); + Integer[] ints = new Integer[length]; + for (int i = 0; i < ints.length; i++) { + ints[i] = RANDOM.nextInt(); + } + return Word.fromSymbols(ints); + } + + static void sanityCheck(Analysis analysis) { + for (Integer oracleId : analysis.involvedOracles) { + List seqIds = analysis.sequenceIds.get(oracleId); + Assert.assertNotNull(seqIds); + boolean[] ids = new boolean[seqIds.size()]; + for (Integer seqId : seqIds) { + Assert.assertNotNull(seqId); + Assert.assertTrue(seqId >= 0); + Assert.assertTrue(seqId < ids.length); + Assert.assertFalse(ids[seqId]); + ids[seqId] = true; + } + Integer incAnswers = analysis.incorrectAnswers.get(oracleId); + Assert.assertEquals(incAnswers.intValue(), 0); + } + Assert.assertTrue((analysis.maxBatchSize - analysis.minBatchSize) <= 1); + + Assert.assertTrue(analysis.involvedOracles.size() <= 1 || analysis.minBatchSize >= MIN_BATCH_SIZE); + } + + static TestOutput extractSULOutput(Word output) { + assert output.size() > 0; + + final TestSULOutput lastSym = output.lastSymbol(); + final int oracleId = lastSym.oracleId; + final int batchSeqId = lastSym.batchSeqId; + + final Word word = lastSym.word; + final Word prefix = word.prefix(word.size() - output.size()); + final Word suffix = word.subWord(word.size() - output.size()); + + return new TestOutput(oracleId, batchSeqId, prefix, suffix); + } + + static final class Analysis { + + final List involvedOracles; + final Map> sequenceIds; + final Map incorrectAnswers; + final int minBatchSize; + final int maxBatchSize; + + Analysis(List involvedOracles, + Map> sequenceIds, + Map incorrectAnswers, + int minBatchSize, + int maxBatchSize) { + this.involvedOracles = involvedOracles; + this.sequenceIds = sequenceIds; + this.incorrectAnswers = incorrectAnswers; + this.minBatchSize = minBatchSize; + this.maxBatchSize = maxBatchSize; + } + } + + static final class TestSULOutput { + + final int oracleId; + final int batchSeqId; + final Word word; + + TestSULOutput(int oracleId, int batchSeqId, Word word) { + this.oracleId = oracleId; + this.batchSeqId = batchSeqId; + this.word = word; + } + } +} diff --git a/oracles/pom.xml b/oracles/pom.xml index cd33d2c398..d012b39652 100644 --- a/oracles/pom.xml +++ b/oracles/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/oracles/property-oracles/pom.xml b/oracles/property-oracles/pom.xml index 8c19d26e8e..8940a62467 100644 --- a/oracles/property-oracles/pom.xml +++ b/oracles/property-oracles/pom.xml @@ -5,7 +5,7 @@ de.learnlib learnlib-oracles-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/pom.xml b/pom.xml index 11d175f31c..4146d5056a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ limitations under the License. --> de.learnlib learnlib-parent - 0.15.0 + 0.16.0 pom LearnLib @@ -149,7 +149,7 @@ limitations under the License. scm:git:git@github.com:LearnLib/learnlib.git scm:git:git@github.com:LearnLib/learnlib.git https://github.com/LearnLib/learnlib/tree/develop - learnlib-0.15.0 + learnlib-0.16.0 https://github.com/LearnLib/learnlib/issues @@ -191,44 +191,45 @@ limitations under the License. 1.8 - 3.0.1 - 3.1.0 - 3.0.0 - 3.8.0 + 3.2.0 + 3.3.0 + 3.1.1 + 3.8.1 4.3.0 - 3.1.1 + 3.1.2 3.0.0-M1 - 3.0.0-M2 - 2.22.1 + 3.0.0-M3 + 3.0.0-M4 1.6 - 3.0.0 - 0.8.4 - 3.1.0 + 3.1.1 + 0.8.6 + 3.2.0 + 3.0.2 1.0.0 - 3.12.0 - 2.5.3 - 3.0.2 + 3.13.0 + 3.0.0-M1 + 3.2.0 3.0.0 - 3.7.1 - 3.0.1 - 3.1.12.2 - 3.0.0-M3 + 3.9.1 + 3.2.1 + 4.1.3 + 3.0.0-M5 1.1.0 0.0.2 - 0.9.0 + 0.10.0 0.1 1.9 - 3.0.1 - 8.19 + 3.7.0 + 8.36.2 9+181-r4173-1 - 28.2-jre - 1.11.1 - 1.43 + 29.0-jre + 1.13.0 + 1.49 1.2.3 1.8 - 3.2.0 + 3.5.13 1.7.30 7.1.0 diff --git a/test-support/learner-it-support/pom.xml b/test-support/learner-it-support/pom.xml index 81a46b9b42..0279ec3ecf 100644 --- a/test-support/learner-it-support/pom.xml +++ b/test-support/learner-it-support/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib.testsupport learnlib-test-support-parent - 0.15.0 + 0.16.0 ../pom.xml @@ -87,6 +87,11 @@ limitations under the License. automata-util + + org.checkerframework + checker-qual + + org.slf4j slf4j-api diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/AbstractVisualizationTest.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/AbstractVisualizationTest.java index e2cb74d0d7..ccbf94660a 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/AbstractVisualizationTest.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/AbstractVisualizationTest.java @@ -16,6 +16,7 @@ package de.learnlib.testsupport; import java.io.IOException; +import java.io.InputStream; import com.google.common.io.CharStreams; import de.learnlib.api.SUL; @@ -30,6 +31,7 @@ import net.automatalib.commons.util.IOUtil; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; +import org.checkerframework.checker.initialization.qual.UnderInitialization; /** * Abstract class for tests that check the visualization of hypotheses or other internal data structure. This class' @@ -58,8 +60,13 @@ public AbstractVisualizationTest() { } protected String resourceAsString(String resourceName) throws IOException { - return CharStreams.toString(IOUtil.asBufferedUTF8Reader(getClass().getResourceAsStream(resourceName))); + try (InputStream is = getClass().getResourceAsStream(resourceName)) { + assert is != null; + return CharStreams.toString(IOUtil.asBufferedUTF8Reader(is)); + } } - protected abstract L getLearnerBuilder(Alphabet alphabet, SUL sul); + protected abstract L getLearnerBuilder(@UnderInitialization AbstractVisualizationTest this, + Alphabet alphabet, + SUL sul); } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantITCase.java index 51a34f315e..178712b3f2 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantITCase.java @@ -52,10 +52,11 @@ public void testLearning() { LearningAlgorithm learner = variant.getLearner(); Alphabet alphabet = example.getAlphabet(); + M reference = example.getReferenceAutomaton(); int maxRounds = variant.getMaxRounds(); if (maxRounds < 0) { - maxRounds = example.getReferenceAutomaton().size(); + maxRounds = reference.size(); } long start = System.nanoTime(); @@ -75,9 +76,10 @@ public void testLearning() { Assert.assertTrue(refined, "Real counterexample " + ceQuery.getInput() + " did not refine hypothesis"); } - Assert.assertNull(Automata.findSeparatingWord(example.getReferenceAutomaton(), - learner.getHypothesisModel(), - alphabet), "Final hypothesis does not match reference automaton"); + M hypothesis = learner.getHypothesisModel(); + Assert.assertEquals(hypothesis.size(), reference.size()); + Assert.assertNull(Automata.findSeparatingWord(reference, hypothesis, alphabet), + "Final hypothesis does not match reference automaton"); long duration = (System.nanoTime() - start) / NANOS_PER_MILLISECOND; LOGGER.info("Passed learner integration test {} ... took [{}]", diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/VPDALearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/VPDALearnerITCase.java index 494dda0757..c8a9d9bacc 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/VPDALearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/VPDALearnerITCase.java @@ -62,7 +62,9 @@ public void testLearning() { Assert.assertTrue(refined, "Real counterexample " + ceQuery.getInput() + " did not refine hypothesis"); } - Assert.assertTrue(Automata.testEquivalence(reference, learner.getHypothesisModel(), alphabet), + OneSEVPA hypothesis = learner.getHypothesisModel(); + Assert.assertEquals(hypothesis.size(), reference.size()); + Assert.assertTrue(Automata.testEquivalence(reference, hypothesis, alphabet), "Final hypothesis does not match reference automaton"); long duration = (System.nanoTime() - start) / NANOS_PER_MILLISECOND; diff --git a/test-support/learning-examples/pom.xml b/test-support/learning-examples/pom.xml index 9f5bb62c37..2d8c65a70c 100644 --- a/test-support/learning-examples/pom.xml +++ b/test-support/learning-examples/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib.testsupport learnlib-test-support-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/dfa/DFABenchmarks.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/dfa/DFABenchmarks.java index b64fc7c454..54e00de89d 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/examples/dfa/DFABenchmarks.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/dfa/DFABenchmarks.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.Objects; import de.learnlib.examples.DefaultLearningExample.DefaultDFALearningExample; import de.learnlib.examples.LearningExample.DFALearningExample; @@ -54,22 +53,28 @@ private DFABenchmarks() { } public static DFALearningExample loadPots2() { - return Objects.requireNonNull(loadLearnLibV2Benchmark("pots2"), - "Couldn't find 'pots2'. Are the correct JARs loaded?"); + return load("pots2"); } public static DFALearningExample loadPots3() { - return Objects.requireNonNull(loadLearnLibV2Benchmark("pots3"), - "Couldn't find 'pots3'. Are the correct JARs loaded?"); + return load("pots3"); } public static DFALearningExample loadPeterson2() { - return Objects.requireNonNull(loadLearnLibV2Benchmark("peterson2"), - "Couldn't find 'peterson2'. Are the correct JARs loaded?"); + return load("peterson2"); } public static DFALearningExample loadPeterson3() { - return Objects.requireNonNull(loadLearnLibV2Benchmark("peterson3"), - "Couldn't find 'peterson3'. Are the correct JARs loaded?"); + return load("peterson3"); + } + + private static DFALearningExample load(String id) { + final DFALearningExample benchmark = loadLearnLibV2Benchmark(id); + + if (benchmark == null) { + throw new IllegalStateException("Couldn't find '" + id + "'. Are the correct JARs loaded?"); + } + + return benchmark; } } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/examples/mealy/ExampleRandomStateLocalInputMealy.java b/test-support/learning-examples/src/main/java/de/learnlib/examples/mealy/ExampleRandomStateLocalInputMealy.java index d684058ceb..1d3b2fd78b 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/examples/mealy/ExampleRandomStateLocalInputMealy.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/examples/mealy/ExampleRandomStateLocalInputMealy.java @@ -19,8 +19,8 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Objects; import java.util.Random; -import java.util.Set; import de.learnlib.examples.LearningExample.StateLocalInputMealyLearningExample; import net.automatalib.automata.transducers.MealyMachine; @@ -29,6 +29,7 @@ import net.automatalib.commons.util.mappings.Mapping; import net.automatalib.commons.util.mappings.MutableMapping; import net.automatalib.commons.util.random.RandomUtil; +import net.automatalib.util.automata.Automata; import net.automatalib.util.automata.random.RandomAutomata; import net.automatalib.words.Alphabet; import org.checkerframework.checker.nullness.qual.Nullable; @@ -56,38 +57,40 @@ public ExampleRandomStateLocalInputMealy(Random random, this.alphabet = alphabet; this.undefinedOutput = undefinedOutput; - CompactMealy source = RandomAutomata.randomDeterministic(random, - size, - alphabet, - Collections.emptyList(), - Arrays.asList(outputs), - new CompactMealy<>(alphabet)); + CompactMealy source = RandomAutomata.randomMealy(random, size, alphabet, Arrays.asList(outputs)); final int alphabetSize = alphabet.size(); final Collection oldStates = source.getStates(); final Integer sink = source.addState(); - final MutableMapping> enabledInputs = source.createDynamicStateMapping(); for (final Integer s : oldStates) { - - final Set stateInputs = new HashSet<>(alphabet); // randomly remove (redirect to sink) transitions for (int idx : RandomUtil.distinctIntegers(random.nextInt(alphabetSize), alphabetSize, random)) { - final I sym = alphabet.getSymbol(idx); - stateInputs.remove(sym); - source.setTransition(s, sym, sink, undefinedOutput); + source.setTransition(s, idx, sink, undefinedOutput); } - enabledInputs.put(s, stateInputs); } - // configure sink for (final I i : alphabet) { source.addTransition(sink, i, sink, undefinedOutput); } - enabledInputs.put(sink, Collections.emptyList()); - this.referenceAutomaton = new MockedSLIMealy<>(source, enabledInputs); + final CompactMealy minimized = Automata.invasiveMinimize(source, alphabet); + final MutableMapping> enabledInputs = source.createStaticStateMapping(); + + for (Integer s : minimized) { + final Collection stateInputs = new HashSet<>(alphabet); + + for (I i : alphabet) { + if (Objects.equals(undefinedOutput, minimized.getOutput(s, i))) { + stateInputs.remove(i); + } + } + + enabledInputs.put(s, stateInputs); + } + + this.referenceAutomaton = new MockedSLIMealy<>(minimized, enabledInputs); } @SafeVarargs @@ -117,16 +120,17 @@ public O getUndefinedOutput() { private static class MockedSLIMealy implements StateLocalInputMealyMachine { private final MealyMachine delegate; - private final Mapping> localInputs; + private final Mapping> localInputs; - MockedSLIMealy(MealyMachine delegate, Mapping> localInputs) { + MockedSLIMealy(MealyMachine delegate, Mapping> localInputs) { this.delegate = delegate; this.localInputs = localInputs; } @Override public Collection getLocalInputs(S state) { - return this.localInputs.get(state); + final Collection collection = this.localInputs.get(state); + return collection == null ? Collections.emptyList() : collection; } @Override @@ -139,9 +143,8 @@ public O getTransitionOutput(T transition) { return this.delegate.getTransitionOutput(transition); } - @Nullable @Override - public T getTransition(S state, I input) { + public @Nullable T getTransition(S state, I input) { return this.delegate.getTransition(state, input); } @@ -150,9 +153,8 @@ public S getSuccessor(T transition) { return this.delegate.getSuccessor(transition); } - @Nullable @Override - public S getInitialState() { + public @Nullable S getInitialState() { return this.delegate.getInitialState(); } } diff --git a/test-support/pom.xml b/test-support/pom.xml index 8150bb92f5..7a083f6ecc 100644 --- a/test-support/pom.xml +++ b/test-support/pom.xml @@ -21,7 +21,7 @@ limitations under the License. de.learnlib learnlib-build-parent - 0.15.0 + 0.16.0 ../build-parent/pom.xml diff --git a/test-support/test-support/pom.xml b/test-support/test-support/pom.xml index 0dd910b593..4bfcea2161 100644 --- a/test-support/test-support/pom.xml +++ b/test-support/test-support/pom.xml @@ -5,7 +5,7 @@ de.learnlib.testsupport learnlib-test-support-parent - 0.15.0 + 0.16.0 ../pom.xml diff --git a/test-support/test-support/src/main/java/de/learnlib/testsupport/AbstractBFOracleTest.java b/test-support/test-support/src/main/java/de/learnlib/testsupport/AbstractBFOracleTest.java index 1620a18d18..8c14977b35 100644 --- a/test-support/test-support/src/main/java/de/learnlib/testsupport/AbstractBFOracleTest.java +++ b/test-support/test-support/src/main/java/de/learnlib/testsupport/AbstractBFOracleTest.java @@ -22,6 +22,7 @@ import net.automatalib.words.impl.Alphabets; import org.mockito.MockitoAnnotations; import org.testng.Assert; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -33,20 +34,26 @@ public abstract class AbstractBFOracleTest { public static final Alphabet ALPHABET = Alphabets.singleton('a'); - public static final double MULTIPLIER = 2.0; + private AutoCloseable mock; + private AbstractBFOracle, Character, D> bfo; - protected abstract AbstractBFOracle, Character, D> createBreadthFirstOracle( - double multiplier); + protected abstract AbstractBFOracle, Character, D> createBreadthFirstOracle(double multiplier); @BeforeMethod public void setUp() { - MockitoAnnotations.initMocks(this); + mock = MockitoAnnotations.openMocks(this); bfo = createBreadthFirstOracle(MULTIPLIER); } + @AfterMethod + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + public void tearDown() throws Exception { + this.mock.close(); + } + @Test public void testGetMultiplier() { Assert.assertEquals(bfo.getMultiplier(), MULTIPLIER);