Java tutorial
/* Copyright (C) 2013 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.baselinelstar; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; import com.google.common.collect.Lists; /** * The internal storage mechanism for {@link BaselineLStar}. * * @param <I> * input symbol class. */ public class ObservationTable<I> implements de.learnlib.algorithms.features.observationtable.ObservationTable<I, Boolean> { @Nonnull private final List<ObservationTableRow<I>> shortPrefixRows; // S @Nonnull private final List<ObservationTableRow<I>> longPrefixRows; // SA @Nonnull private final List<Word<I>> suffixes; // E public ObservationTable() { Word<I> emptyWord = Word.epsilon(); suffixes = new ArrayList<>(); suffixes.add(emptyWord); Word<I> epsiplon = Word.epsilon(); ObservationTableRow<I> initialRow = new ObservationTableRow<>(epsiplon); initialRow.setShortPrefixRow(); shortPrefixRows = new ArrayList<>(); shortPrefixRows.add(initialRow); longPrefixRows = new ArrayList<>(); } /** * The set of suffixes in the observation table, often called "E". * * @return The set of candidates. */ @Override public List<Word<I>> getSuffixes() { return Collections.unmodifiableList(suffixes); } void addSuffix(Word<I> suffix) { suffixes.add(suffix); } @Override public Collection<ObservationTableRow<I>> getShortPrefixRows() { return Collections.unmodifiableCollection(shortPrefixRows); } @Nonnull public List<Word<I>> getShortPrefixLabels() { List<Word<I>> labels = Lists.newArrayListWithExpectedSize(shortPrefixRows.size()); for (ObservationTableRow<I> row : shortPrefixRows) { labels.add(row.getLabel()); } return labels; } @Override public Collection<ObservationTableRow<I>> getLongPrefixRows() { return Collections.unmodifiableCollection(longPrefixRows); } @Nonnull public List<Word<I>> getLongPrefixLabels() { List<Word<I>> labels = Lists.newArrayListWithExpectedSize(longPrefixRows.size()); for (ObservationTableRow<I> row : longPrefixRows) { labels.add(row.getLabel()); } return labels; } @Override @Nullable public Row<I, Boolean> getSuccessorRow(@Nonnull Row<I, Boolean> spRow, I symbol) { //noinspection SuspiciousMethodCalls if (!shortPrefixRows.contains(spRow)) { throw new IllegalArgumentException("Row '" + spRow + "' is not part of short prefix rows!"); } Word<I> successorLabel = spRow.getLabel().append(symbol); Row<I, Boolean> successor = null; for (Row<I, Boolean> row : getAllRows()) { if (row.getLabel().equals(successorLabel)) { successor = row; break; } } return successor; } void addShortPrefix(@Nonnull Word<I> shortPrefix) { final ObservationTableRow<I> row = new ObservationTableRow<>(shortPrefix); row.setShortPrefixRow(); shortPrefixRows.add(row); } void addLongPrefix(@Nonnull Word<I> longPrefix) { final ObservationTableRow<I> row = new ObservationTableRow<>(longPrefix); row.setLongPrefixRow(); longPrefixRows.add(row); } void removeShortPrefixesFromLongPrefixes() { List<Word<I>> longPrefixLabels = getLongPrefixLabels(); longPrefixLabels.retainAll(getShortPrefixLabels()); List<ObservationTableRow<I>> rowsToRemove = Lists.newArrayListWithCapacity(longPrefixLabels.size()); for (ObservationTableRow<I> row : longPrefixRows) { if (longPrefixLabels.contains(row.getLabel())) { rowsToRemove.add(row); } } longPrefixRows.removeAll(rowsToRemove); } /** * Adds the result of a membership query to this table. * * @param prefix * The prefix of the {@link Word} asked with the membership query. * @param suffix * The prefix of the {@link Word} asked with the membership query. * @param result * The result of the query. */ void addResult(@Nonnull Word<I> prefix, @Nonnull Word<I> suffix, @Nonnull Boolean result) { if (!suffixes.contains(suffix)) { throw new IllegalArgumentException("Suffix '" + suffix + "' is not part of the suffixes set"); } final int suffixPosition = suffixes.indexOf(suffix); ObservationTableRow<I> row = getRowForPrefix(prefix); addResultToRow(result, suffixPosition, row); } private void addResultToRow(@Nonnull Boolean result, int suffixPosition, @Nonnull ObservationTableRow<I> row) { final List<Boolean> values = row.getContents(); if (values.size() > suffixPosition) { if (!values.get(suffixPosition).equals(result)) { throw new IllegalStateException( "New result " + values.get(suffixPosition) + " differs from old result " + result); } } else { row.addValue(result); } } /** * Determines the next state for which the observation table needs to be closed. * * @return The next state for which the observation table needs to be closed. If the * table is closed, this returns {@code null}. */ @Nullable Word<I> findUnclosedState() { for (ObservationTableRow<I> candidate : longPrefixRows) { boolean found = false; for (ObservationTableRow<I> stateRow : shortPrefixRows) { if (candidate.isContentsEqual(stateRow)) { found = true; break; } } if (!found) { return candidate.getLabel(); } } return null; } /** * @param alphabet * The {@link Alphabet} for which the consistency is checked * @return if the observation table is consistent with the given alphabet. */ boolean isConsistentWithAlphabet(@Nonnull Alphabet<I> alphabet) { return findInconsistentSymbol(alphabet) == null; } @Nullable InconsistencyDataHolder<I> findInconsistentSymbol(@Nonnull Alphabet<I> alphabet) { for (I symbol : alphabet) { for (int firstStateCounter = 0; firstStateCounter < shortPrefixRows.size(); firstStateCounter++) { ObservationTableRow<I> firstRow = shortPrefixRows.get(firstStateCounter); for (int secondStateCounter = firstStateCounter + 1; secondStateCounter < shortPrefixRows .size(); secondStateCounter++) { ObservationTableRow<I> secondRow = shortPrefixRows.get(secondStateCounter); if (checkInconsistency(firstRow, secondRow, symbol)) { return new InconsistencyDataHolder<>(firstRow, secondRow, symbol); } } } } return null; } private boolean checkInconsistency(@Nonnull ObservationTableRow<I> firstRow, @Nonnull ObservationTableRow<I> secondRow, @Nonnull I alphabetSymbol) { if (!firstRow.isContentsEqual(secondRow)) { return false; } Word<I> extendedFirstState = firstRow.getLabel().append(alphabetSymbol); Word<I> extendedSecondState = secondRow.getLabel().append(alphabetSymbol); ObservationTableRow<I> rowForExtendedFirstState = getRowForPrefix(extendedFirstState); ObservationTableRow<I> rowForExtendedSecondState = getRowForPrefix(extendedSecondState); return !rowForExtendedFirstState.isContentsEqual(rowForExtendedSecondState); } @Nonnull Word<I> determineWitnessForInconsistency(@Nonnull InconsistencyDataHolder<I> dataHolder) { Word<I> firstState = dataHolder.getFirstState().append(dataHolder.getDifferingSymbol()); Word<I> secondState = dataHolder.getSecondState().append(dataHolder.getDifferingSymbol()); ObservationTableRow<I> firstRow = getRowForPrefix(firstState); ObservationTableRow<I> secondRow = getRowForPrefix(secondState); final List<Boolean> firstRowContents = firstRow.getContents(); final List<Boolean> secondRowContents = secondRow.getContents(); for (int i = 0; i < firstRow.getContents().size(); i++) { Boolean symbolFirstRow = firstRowContents.get(i); Boolean symbolSecondRow = secondRowContents.get(i); if (!symbolFirstRow.equals(symbolSecondRow)) { return suffixes.get(i); } } throw new IllegalStateException("Both rows are identical, unable to determine a witness!"); } @Nonnull ObservationTableRow<I> getRowForPrefix(@Nonnull Word<I> state) { for (ObservationTableRow<I> row : shortPrefixRows) { if (row.getLabel().equals(state)) { return row; } } for (ObservationTableRow<I> row : longPrefixRows) { if (row.getLabel().equals(state)) { return row; } } throw new IllegalArgumentException("Unable to find a row for '" + state + "'"); } /** * Moves a single row from long prefix rows to short prefix rows. * * @param longPrefix * A row which must be part of the long prefix rows and * should be moved to the short prefix rows. */ void moveLongPrefixToShortPrefixes(@Nonnull Word<I> longPrefix) { ObservationTableRow<I> rowToMove = null; for (ObservationTableRow<I> row : longPrefixRows) { if (row.getLabel().equals(longPrefix)) { rowToMove = row; break; } } if (rowToMove == null) { throw new IllegalArgumentException("Word '" + longPrefix + "' not part of long prefixes"); } longPrefixRows.remove(rowToMove); rowToMove.setShortPrefixRow(); shortPrefixRows.add(rowToMove); } }