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}