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

LearnLib / automatalib / 13138848026

04 Feb 2025 02:53PM UTC coverage: 92.108% (+2.2%) from 89.877%
13138848026

push

github

mtf90
[maven-release-plugin] prepare release automatalib-0.12.0

16609 of 18032 relevant lines covered (92.11%)

1.7 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-2025 TU Dortmund University
2
 * This file is part of AutomataLib <https://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✔
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
        MutableMapping<S, 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, 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