001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of LearnLib, http://www.learnlib.de/. 003 * 004 * LearnLib is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License version 3.0 as published by the Free Software Foundation. 007 * 008 * LearnLib is distributed in the hope that it will be useful, 009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * Lesser General Public License for more details. 012 * 013 * You should have received a copy of the GNU Lesser General Public 014 * License along with LearnLib; if not, see 015 * <http://www.gnu.de/documents/lgpl.en.html>. 016 */ 017package de.learnlib.cache.mealy; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.List; 025 026import net.automatalib.commons.util.comparison.CmpUtil; 027import net.automatalib.commons.util.mappings.Mapping; 028import net.automatalib.incremental.mealy.IncrementalMealyBuilder; 029import net.automatalib.words.Alphabet; 030import net.automatalib.words.Word; 031import net.automatalib.words.WordBuilder; 032import de.learnlib.api.MembershipOracle; 033import de.learnlib.api.Query; 034 035/** 036 * Mealy cache. This cache is implemented as a membership oracle: upon construction, it is 037 * provided with a delegate oracle. Queries that can be answered from the cache are answered 038 * directly, others are forwarded to the delegate oracle. When the delegate oracle has finished 039 * processing these remaining queries, the results are incorporated into the cache. 040 * 041 * This oracle additionally enables the user to define a Mealy-style prefix-closure filter: 042 * a {@link Mapping} from output symbols to output symbols may be provided, with the following 043 * semantics: If in an output word a symbol for which the given mapping has a non-null value 044 * is encountered, all symbols <i>after</i> this symbol are replaced by the respective value. 045 * The rationale behind this is that the concrete error message (key in the mapping) is still 046 * reflected in the learned model, it is forced to result in a sink state with only a single 047 * repeating output symbol (value in the mapping). 048 * 049 * @author Malte Isberner <malte.isberner@gmail.com> 050 * 051 * @param <I> input symbol class 052 * @param <O> output symbol class 053 */ 054public class MealyCacheOracle<I, O> implements MembershipOracle<I, Word<O>> { 055 056 private static final class ReverseLexCmp<I> implements Comparator<Query<I,?>> { 057 private final Alphabet<I> alphabet; 058 059 public ReverseLexCmp(Alphabet<I> alphabet) { 060 this.alphabet = alphabet; 061 } 062 063 @Override 064 public int compare(Query<I, ?> o1, Query<I, ?> o2) { 065 return -CmpUtil.lexCompare(o1.getInput(), o2.getInput(), alphabet); 066 } 067 } 068 069 private final MembershipOracle<I,Word<O>> delegate; 070 private final IncrementalMealyBuilder<I, O> incMealy; 071 private final Comparator<? super Query<I,?>> queryCmp; 072 private final Mapping<? super O,? extends O> errorSyms; 073 074 /** 075 * Constructor. 076 * @param alphabet the input alphabet for the cache 077 * @param delegate the delegate Mealy oracle 078 */ 079 public MealyCacheOracle(Alphabet<I> alphabet, MembershipOracle<I,Word<O>> delegate) { 080 this(alphabet, null, delegate); 081 } 082 083 /** 084 * Constructor. 085 * @param alphabet the input alphabet for the cache 086 * @param errorSyms the error symbol mapping (see class description) 087 * @param delegate the delegate Mealy oracle 088 */ 089 public MealyCacheOracle(Alphabet<I> alphabet, Mapping<? super O, ? extends O> errorSyms, MembershipOracle<I,Word<O>> delegate) { 090 this.delegate = delegate; 091 this.incMealy = new IncrementalMealyBuilder<>(alphabet); 092 this.queryCmp = new ReverseLexCmp<>(alphabet); 093 this.errorSyms = errorSyms; 094 } 095 096 public int getCacheSize() { 097 return incMealy.size(); 098 } 099 100 /** 101 * Creates an equivalence oracle that checks an hypothesis for consistency with the 102 * contents of this cache. Note that the returned oracle is backed by the cache data structure, 103 * i.e., it is sufficient to call this method once after creation of the cache. 104 * @return the cache consistency test backed by the contents of this cache. 105 */ 106 public MealyCacheConsistencyTest<I, O> createCacheConsistencyTest() { 107 return new MealyCacheConsistencyTest<>(incMealy); 108 } 109 110 /* 111 * (non-Javadoc) 112 * @see de.learnlib.api.MembershipOracle#processQueries(java.util.Collection) 113 */ 114 @Override 115 public void processQueries(Collection<? extends Query<I, Word<O>>> queries) { 116 if(queries.isEmpty()) 117 return; 118 119 List<Query<I,Word<O>>> qrys = new ArrayList<Query<I,Word<O>>>(queries); 120 Collections.sort(qrys, queryCmp); 121 122 List<MasterQuery<I,O>> masterQueries = new ArrayList<>(); 123 124 Iterator<Query<I,Word<O>>> it = qrys.iterator(); 125 Query<I,Word<O>> q = it.next(); 126 Word<I> ref = q.getInput(); 127 MasterQuery<I,O> master = createMasterQuery(ref); 128 if(master.getAnswer() == null) 129 masterQueries.add(master); 130 master.addSlave(q); 131 132 while(it.hasNext()) { 133 q = it.next(); 134 Word<I> curr = q.getInput(); 135 if(!curr.isPrefixOf(ref)) { 136 master = createMasterQuery(curr); 137 if(master.getAnswer() == null) 138 masterQueries.add(master); 139 } 140 141 master.addSlave(q); 142 ref = curr; 143 } 144 145 delegate.processQueries(masterQueries); 146 147 for(MasterQuery<I,O> m : masterQueries) 148 postProcess(m); 149 } 150 151 private void postProcess(MasterQuery<I,O> master) { 152 Word<I> word = master.getSuffix(); 153 Word<O> answer = master.getAnswer(); 154 155 if(errorSyms == null) { 156 incMealy.insert(word, answer); 157 return; 158 } 159 160 int answLen = answer.length(); 161 int i = 0; 162 while(i < answLen) { 163 O sym = answer.getSymbol(i++); 164 if(errorSyms.get(sym) != null) 165 break; 166 } 167 168 if(i == answLen) 169 incMealy.insert(word, answer); 170 else 171 incMealy.insert(word.prefix(i), answer.prefix(i)); 172 } 173 174 private MasterQuery<I,O> createMasterQuery(Word<I> word) { 175 WordBuilder<O> wb = new WordBuilder<O>(); 176 if(incMealy.lookup(word, wb)) 177 return new MasterQuery<>(word, wb.toWord()); 178 179 if(errorSyms == null) 180 return new MasterQuery<>(word); 181 int wbSize = wb.size(); 182 O repSym; 183 if(wbSize == 0 || (repSym = errorSyms.get(wb.getSymbol(wbSize - 1))) == null) 184 return new MasterQuery<>(word, errorSyms); 185 186 wb.repeatAppend(word.length() - wbSize, repSym); 187 return new MasterQuery<>(word, wb.toWord()); 188 } 189 190}