• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LearnLib / automatalib / 12651580329

07 Jan 2025 12:29PM UTC coverage: 91.569% (+0.03%) from 91.542%
12651580329

push

github

web-flow
Update dependencies (#85)

* bump basic dependency versions

* bump checkstyle + cleanups

* bump spotbugs + cleanups

* bump pmd + cleanups

* bump checkerframework + cleanups

* some more cleanups

* ExceptionUtil: support nulls

* improve comments

* cleanup naming + formatting

* formatting

* formatting

* do not fail on javadoc warnings

completness of documentation is now checked by checkstyle and we would have to disable failing anyways when moving on to JDK 17

192 of 217 new or added lines in 63 files covered. (88.48%)

4 existing lines in 4 files now uncovered.

16573 of 18099 relevant lines covered (91.57%)

1.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.38
/incremental/src/main/java/net/automatalib/incremental/dfa/tree/IncrementalPCDFATreeBuilder.java
1
/* Copyright (C) 2013-2024 TU Dortmund University
2
 * This file is part of AutomataLib, http://www.automatalib.net/.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.automatalib.incremental.dfa.tree;
17

18
import java.util.ArrayDeque;
19
import java.util.Collection;
20
import java.util.Deque;
21
import java.util.Iterator;
22

23
import net.automatalib.alphabet.Alphabet;
24
import net.automatalib.automaton.fsa.DFA;
25
import net.automatalib.automaton.graph.TransitionEdge;
26
import net.automatalib.automaton.graph.UniversalAutomatonGraphView;
27
import net.automatalib.common.util.mapping.MutableMapping;
28
import net.automatalib.graph.Graph;
29
import net.automatalib.incremental.ConflictException;
30
import net.automatalib.incremental.dfa.AbstractVisualizationHelper;
31
import net.automatalib.incremental.dfa.Acceptance;
32
import net.automatalib.ts.UniversalDTS;
33
import net.automatalib.visualization.VisualizationHelper;
34
import net.automatalib.word.Word;
35
import net.automatalib.word.WordBuilder;
36
import org.checkerframework.checker.nullness.qual.NonNull;
37
import org.checkerframework.checker.nullness.qual.Nullable;
38

39
/**
40
 * The prefix-closed version of {@link IncrementalDFATreeBuilder}. Contrary to the regular lookup semantics, where an
41
 * exact response to a lookup can only be given, if the exact word has been observed before, the prefix-closed semantics
42
 * behave as follows:
43
 *
44
 * <ul>
45
 * <li>prefixes of previously observed accepted words will result in a {@link Acceptance#TRUE} response as well.</li>
46
 * <li>continuations of previously observed rejected words will result in a {@link Acceptance#FALSE} response as
47
 * well.</li>
48
 * </ul>
49
 *
50
 * @param <I>
51
 *         input symbol class
52
 */
53
public class IncrementalPCDFATreeBuilder<I> extends IncrementalDFATreeBuilder<I> {
2✔
54

55
    private @Nullable Node sink;
56

57
    public IncrementalPCDFATreeBuilder(Alphabet<I> alphabet) {
58
        super(alphabet);
2✔
59
    }
2✔
60

61
    @Override
62
    <S> @Nullable Word<I> doFindSeparatingWord(DFA<S, I> target,
63
                                               Collection<? extends I> inputs,
64
                                               boolean omitUndefined) {
65

66
        S automatonInit = target.getInitialState();
2✔
67
        if (automatonInit == null) {
2✔
NEW
68
            return omitUndefined ? null : Word.epsilon();
×
69
        }
70

71
        Acceptance rootAcc = root.getAcceptance();
2✔
72
        if (rootAcc.conflicts(target.isAccepting(automatonInit))) {
2✔
73
            return Word.epsilon();
×
74
        }
75
        if (rootAcc == Acceptance.FALSE) {
2✔
76
            return findLive(target, automatonInit, inputs, target.createStaticStateMapping());
×
77
        }
78

79
        // incomingInput can be null here, because we will always skip the bottom stack element below
80
        @SuppressWarnings("nullness")
81
        Record<@Nullable S, I> init = new Record<>(automatonInit, root, null, inputs.iterator());
2✔
82

83
        Deque<Record<@Nullable S, I>> dfsStack = new ArrayDeque<>();
2✔
84
        dfsStack.push(init);
2✔
85

86
        @Nullable MutableMapping<S, @Nullable Boolean> deadStates = null;
2✔
87

88
        while (!dfsStack.isEmpty()) {
2✔
89
            @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
90
            @NonNull Record<@Nullable S, I> rec = dfsStack.peek();
2✔
91
            if (!rec.inputIt.hasNext()) {
2✔
92
                dfsStack.pop();
2✔
93
                continue;
2✔
94
            }
95
            I input = rec.inputIt.next();
2✔
96
            int inputIdx = inputAlphabet.getSymbolIndex(input);
2✔
97

98
            Node succ = rec.treeNode.getChild(inputIdx);
2✔
99
            if (succ == null) {
2✔
100
                continue;
2✔
101
            }
102

103
            Acceptance acc = succ.getAcceptance();
2✔
104

105
            S automatonSucc = (rec.automatonState == null) ? null : target.getTransition(rec.automatonState, input);
2✔
106
            if (automatonSucc == null && (omitUndefined || acc == Acceptance.FALSE)) {
2✔
107
                continue;
2✔
108
            }
109

110
            boolean succAcc = automatonSucc != null && target.isAccepting(automatonSucc);
2✔
111

112
            Word<I> liveSuffix = null;
2✔
113
            if (automatonSucc != null && acc == Acceptance.FALSE) {
2✔
114
                if (deadStates == null) {
2✔
115
                    deadStates = target.createStaticStateMapping();
2✔
116
                }
117
                liveSuffix = findLive(target, automatonSucc, inputs, deadStates);
2✔
118
            }
119

120
            if (acc.conflicts(succAcc) || liveSuffix != null) {
2✔
121
                WordBuilder<I> wb = new WordBuilder<>(dfsStack.size());
2✔
122
                wb.append(input);
2✔
123

124
                dfsStack.pop();
2✔
125
                while (!dfsStack.isEmpty()) {
2✔
126
                    wb.append(rec.incomingInput);
2✔
127
                    rec = dfsStack.pop();
2✔
128
                }
129
                wb.reverse();
2✔
130
                if (liveSuffix != null) {
2✔
131
                    wb.append(liveSuffix);
2✔
132
                }
133
                return wb.toWord();
2✔
134
            }
135

136
            dfsStack.push(new Record<>(automatonSucc, succ, input, inputs.iterator()));
2✔
137
        }
2✔
138

139
        return null;
2✔
140
    }
141

142
    @Override
143
    public Acceptance lookup(Word<? extends I> inputWord) {
144
        Node curr = root;
2✔
145

146
        for (I sym : inputWord) {
2✔
147
            if (curr.getAcceptance() == Acceptance.FALSE) {
2✔
148
                return Acceptance.FALSE;
2✔
149
            }
150

151
            int symIdx = inputAlphabet.getSymbolIndex(sym);
2✔
152
            Node succ = curr.getChild(symIdx);
2✔
153
            if (succ == null) {
2✔
154
                return Acceptance.DONT_KNOW;
2✔
155
            }
156
            curr = succ;
2✔
157
        }
2✔
158
        return curr.getAcceptance();
2✔
159
    }
160

161
    @Override
162
    public void insert(Word<? extends I> word, boolean acceptance) {
163
        if (acceptance) {
2✔
164
            insertTrue(word);
2✔
165
        } else {
166
            insertFalse(word);
2✔
167
        }
168
    }
2✔
169

170
    @Override
171
    public UniversalDTS<?, I, ?, Acceptance, Void> asTransitionSystem() {
172
        return new TransitionSystemView();
2✔
173
    }
174

175
    @Override
176
    public Graph<?, ?> asGraph() {
177
        return new UniversalAutomatonGraphView<Node, I, Node, Acceptance, Void, TransitionSystemView>(new TransitionSystemView(),
2✔
178
                                                                                                      inputAlphabet) {
2✔
179

180
            @Override
181
            public VisualizationHelper<Node, TransitionEdge<I, Node>> getVisualizationHelper() {
182
                return new AbstractVisualizationHelper<Node, I, Node, TransitionSystemView>(automaton) {
2✔
183

184
                    @Override
185
                    public Acceptance getAcceptance(Node node) {
186
                        return node.getAcceptance();
2✔
187
                    }
188
                };
189
            }
190
        };
191
    }
192

193
    private void insertTrue(Word<? extends I> word) {
194
        Node curr = root;
2✔
195

196
        int idx = 0;
2✔
197
        for (I sym : word) {
2✔
198
            if (curr.getAcceptance() == Acceptance.FALSE) {
2✔
199
                throw new ConflictException("Conflicting acceptance values for word " + word.prefix(idx) +
2✔
200
                                            ": found FALSE, expected DONT_KNOW or TRUE");
201
            }
202
            curr.setAcceptance(Acceptance.TRUE);
2✔
203
            int symIdx = inputAlphabet.getSymbolIndex(sym);
2✔
204
            Node succ = curr.getChild(symIdx);
2✔
205
            if (succ == null) {
2✔
206
                succ = new Node(Acceptance.TRUE);
2✔
207
                curr.setChild(symIdx, alphabetSize, succ);
2✔
208
            }
209
            curr = succ;
2✔
210
            idx++;
2✔
211
        }
2✔
212
        if (curr.getAcceptance() == Acceptance.FALSE) {
2✔
213
            throw new ConflictException(
×
214
                    "Conflicting acceptance values for word " + word + ": found FALSE, expected DONT_KNOW or TRUE");
215
        }
216
        curr.setAcceptance(Acceptance.TRUE);
2✔
217
    }
2✔
218

219
    private void insertFalse(Word<? extends I> word) {
220
        Node curr = root;
2✔
221
        Node prev = null;
2✔
222
        int lastSymIdx = -1;
2✔
223

224
        for (I sym : word) {
2✔
225
            if (curr.getAcceptance() == Acceptance.FALSE) {
2✔
226
                return; // done!
×
227
            }
228
            int symIdx = inputAlphabet.getSymbolIndex(sym);
2✔
229
            Node succ = curr.getChild(symIdx);
2✔
230
            if (succ == null) {
2✔
231
                succ = new Node(Acceptance.DONT_KNOW);
2✔
232
                curr.setChild(symIdx, alphabetSize, succ);
2✔
233
            }
234
            prev = curr;
2✔
235
            curr = succ;
2✔
236
            lastSymIdx = symIdx;
2✔
237
        }
2✔
238

239
        if (curr.getAcceptance() == Acceptance.TRUE) {
2✔
240
            throw new ConflictException(
2✔
241
                    "Conflicting acceptance values for word " + word + ": found TRUE, expected DONT_KNOW or FALSE");
242
        }
243

244
        // Note that we do not need to look deeper into the tree, because
245
        // if any of the successor of curr would have an acceptance value
246
        // of true, also curr would
247
        if (prev == null) {
2✔
248
            assert curr == root;
2✔
249
            root.makeSink();
2✔
250
        } else {
251
            Node sink = getSink();
2✔
252
            prev.setChild(lastSymIdx, alphabetSize, sink);
2✔
253
        }
254
    }
2✔
255

256
    private Node getSink() {
257
        if (sink == null) {
2✔
258
            sink = new Node(Acceptance.FALSE);
2✔
259
        }
260
        return sink;
2✔
261
    }
262

263
    private static <S, I> @Nullable Word<I> findLive(DFA<S, I> dfa,
264
                                                     S state,
265
                                                     Collection<? extends I> inputs,
266
                                                     MutableMapping<S, @Nullable Boolean> deadStates) {
267
        if (dfa.isAccepting(state)) {
2✔
268
            return Word.epsilon();
×
269
        }
270

271
        Boolean dead = deadStates.get(state);
2✔
272
        if (dead != null && dead) {
2✔
273
            return null;
×
274
        }
275
        deadStates.put(state, true);
2✔
276

277
        // incomingInput can be null here, because we will always skip the bottom stack element below
278
        @SuppressWarnings("nullness")
279
        FindLiveRecord<@Nullable S, I> init = new FindLiveRecord<>(state, null, inputs.iterator());
2✔
280

281
        Deque<FindLiveRecord<@Nullable S, I>> dfsStack = new ArrayDeque<>();
2✔
282
        dfsStack.push(init);
2✔
283

284
        while (!dfsStack.isEmpty()) {
2✔
285
            @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
286
            @NonNull FindLiveRecord<@Nullable S, I> rec = dfsStack.peek();
2✔
287
            if (!rec.inputIt.hasNext()) {
2✔
288
                dfsStack.pop();
2✔
289
                continue;
2✔
290
            }
291
            I input = rec.inputIt.next();
2✔
292

293
            @Nullable S s = rec.state;
2✔
294
            @Nullable S succ = s == null ? null : dfa.getTransition(s, input);
2✔
295
            if (succ == null) {
2✔
296
                continue;
2✔
297
            }
298
            if (dfa.isAccepting(succ)) {
2✔
299
                WordBuilder<I> wb = new WordBuilder<>(dfsStack.size());
2✔
300
                wb.append(input);
2✔
301

302
                dfsStack.pop();
2✔
303
                while (!dfsStack.isEmpty()) {
2✔
304
                    wb.append(rec.incomingInput);
×
305
                    rec = dfsStack.pop();
×
306
                }
307
                return wb.reverse().toWord();
2✔
308
            }
309

310
            dead = deadStates.get(succ);
×
311
            if (dead == null) {
×
312
                dfsStack.push(new FindLiveRecord<>(succ, input, inputs.iterator()));
×
313
                deadStates.put(succ, true);
×
314
            } else {
315
                assert dead;
×
316
            }
317
        }
×
318

319
        return null;
2✔
320
    }
321

322
    private static final class FindLiveRecord<S, I> {
323

324
        public final S state;
325
        public final I incomingInput;
326
        public final Iterator<? extends I> inputIt;
327

328
        FindLiveRecord(S state, I incomingInput, Iterator<? extends I> inputIt) {
2✔
329
            this.state = state;
2✔
330
            this.incomingInput = incomingInput;
2✔
331
            this.inputIt = inputIt;
2✔
332
        }
2✔
333
    }
334

335
    private final class TransitionSystemView extends IncrementalDFATreeBuilder<I>.TransitionSystemView {
2✔
336

337
        @Override
338
        public @Nullable Node getTransition(Node state, I input) {
339
            if (state.getAcceptance() == Acceptance.FALSE) {
2✔
340
                return state;
2✔
341
            }
342
            return super.getTransition(state, input);
2✔
343
        }
344
    }
345

346
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc