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

LearnLib / automatalib / 6763327895

05 Nov 2023 07:29PM UTC coverage: 89.726% (-0.1%) from 89.868%
6763327895

push

github

mtf90
fix typo

4 of 4 new or added lines in 4 files covered. (100.0%)

99 existing lines in 18 files now uncovered.

15677 of 17472 relevant lines covered (89.73%)

1.66 hits per line

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

90.91
/incremental/src/main/java/net/automatalib/incremental/dfa/tree/IncrementalPCDFATreeBuilder.java
1
/* Copyright (C) 2013-2023 TU Dortmund
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
        Acceptance rootAcc = root.getAcceptance();
2✔
68
        if (rootAcc.conflicts(automatonInit != null && target.isAccepting(automatonInit))) {
2✔
UNCOV
69
            return Word.epsilon();
×
70
        }
71
        if (rootAcc == Acceptance.FALSE) {
2✔
UNCOV
72
            return findLive(target, automatonInit, inputs, target.createStaticStateMapping());
×
73
        }
74

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

79
        Deque<Record<@Nullable S, I>> dfsStack = new ArrayDeque<>();
2✔
80
        dfsStack.push(init);
2✔
81

82
        @Nullable MutableMapping<S, @Nullable Boolean> deadStates = null;
2✔
83

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

94
            Node succ = rec.treeNode.getChild(inputIdx);
2✔
95
            if (succ == null) {
2✔
96
                continue;
2✔
97
            }
98

99
            Acceptance acc = succ.getAcceptance();
2✔
100

101
            S automatonSucc = (rec.automatonState == null) ? null : target.getTransition(rec.automatonState, input);
2✔
102
            if (automatonSucc == null && (omitUndefined || acc == Acceptance.FALSE)) {
2✔
103
                continue;
2✔
104
            }
105

106
            boolean succAcc = automatonSucc != null && target.isAccepting(automatonSucc);
2✔
107

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

116
            if (acc.conflicts(succAcc) || liveSuffix != null) {
2✔
117
                WordBuilder<I> wb = new WordBuilder<>(dfsStack.size());
2✔
118
                wb.append(input);
2✔
119

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

132
            dfsStack.push(new Record<>(automatonSucc, succ, input, inputs.iterator()));
2✔
133
        }
2✔
134

135
        return null;
2✔
136
    }
137

138
    @Override
139
    public Acceptance lookup(Word<? extends I> inputWord) {
140
        Node curr = root;
2✔
141

142
        for (I sym : inputWord) {
2✔
143
            if (curr.getAcceptance() == Acceptance.FALSE) {
2✔
144
                return Acceptance.FALSE;
2✔
145
            }
146

147
            int symIdx = inputAlphabet.getSymbolIndex(sym);
2✔
148
            Node succ = curr.getChild(symIdx);
2✔
149
            if (succ == null) {
2✔
150
                return Acceptance.DONT_KNOW;
2✔
151
            }
152
            curr = succ;
2✔
153
        }
2✔
154
        return curr.getAcceptance();
2✔
155
    }
156

157
    @Override
158
    public void insert(Word<? extends I> word, boolean acceptance) {
159
        if (acceptance) {
2✔
160
            insertTrue(word);
2✔
161
        } else {
162
            insertFalse(word);
2✔
163
        }
164
    }
2✔
165

166
    @Override
167
    public UniversalDTS<?, I, ?, Acceptance, Void> asTransitionSystem() {
168
        return new TransitionSystemView();
2✔
169
    }
170

171
    @Override
172
    public Graph<?, ?> asGraph() {
173
        return new UniversalAutomatonGraphView<Node, I, Node, Acceptance, Void, TransitionSystemView>(new TransitionSystemView(),
2✔
174
                                                                                                      inputAlphabet) {
2✔
175

176
            @Override
177
            public VisualizationHelper<Node, TransitionEdge<I, Node>> getVisualizationHelper() {
178
                return new AbstractVisualizationHelper<Node, I, Node, TransitionSystemView>(automaton) {
2✔
179

180
                    @Override
181
                    public Acceptance getAcceptance(Node node) {
182
                        return node.getAcceptance();
2✔
183
                    }
184
                };
185
            }
186
        };
187
    }
188

189
    private void insertTrue(Word<? extends I> word) {
190
        Node curr = root;
2✔
191

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

215
    private void insertFalse(Word<? extends I> word) {
216
        Node curr = root;
2✔
217
        Node prev = null;
2✔
218
        int lastSymIdx = -1;
2✔
219

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

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

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

252
    private Node getSink() {
253
        if (sink == null) {
2✔
254
            sink = new Node(Acceptance.FALSE);
2✔
255
        }
256
        return sink;
2✔
257
    }
258

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

267
        Boolean dead = deadStates.get(state);
2✔
268
        if (dead != null && dead) {
2✔
UNCOV
269
            return null;
×
270
        }
271
        deadStates.put(state, true);
2✔
272

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

277
        Deque<FindLiveRecord<@Nullable S, I>> dfsStack = new ArrayDeque<>();
2✔
278
        dfsStack.push(init);
2✔
279

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

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

298
                dfsStack.pop();
2✔
299
                while (!dfsStack.isEmpty()) {
2✔
UNCOV
300
                    wb.append(rec.incomingInput);
×
UNCOV
301
                    rec = dfsStack.pop();
×
302
                }
303
                return wb.reverse().toWord();
2✔
304
            }
305

UNCOV
306
            dead = deadStates.get(succ);
×
UNCOV
307
            if (dead == null) {
×
UNCOV
308
                dfsStack.push(new FindLiveRecord<>(succ, input, inputs.iterator()));
×
UNCOV
309
                deadStates.put(succ, true);
×
310
            } else {
UNCOV
311
                assert dead;
×
312
            }
UNCOV
313
        }
×
314

315
        return null;
2✔
316
    }
317

318
    private static final class FindLiveRecord<S, I> {
319

320
        public final S state;
321
        public final I incomingInput;
322
        public final Iterator<? extends I> inputIt;
323

324
        FindLiveRecord(S state, I incomingInput, Iterator<? extends I> inputIt) {
2✔
325
            this.state = state;
2✔
326
            this.incomingInput = incomingInput;
2✔
327
            this.inputIt = inputIt;
2✔
328
        }
2✔
329
    }
330

331
    private class TransitionSystemView extends IncrementalDFATreeBuilder<I>.TransitionSystemView {
2✔
332

333
        @Override
334
        public @Nullable Node getTransition(Node state, I input) {
335
            if (state.getAcceptance() == Acceptance.FALSE) {
2✔
336
                return state;
2✔
337
            }
338
            return super.getTransition(state, input);
2✔
339
        }
340
    }
341

342
}
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