001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of AutomataLib, http://www.automatalib.net/. 003 * 004 * AutomataLib 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 * AutomataLib 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 AutomataLib; if not, see 015 * http://www.gnu.de/documents/lgpl.en.html. 016 */ 017package net.automatalib.incremental.dfa; 018 019import java.util.ArrayDeque; 020import java.util.Deque; 021 022import net.automatalib.incremental.ConflictException; 023import net.automatalib.words.Alphabet; 024import net.automatalib.words.Word; 025 026public class IncrementalPCDFABuilder<I> extends AbstractIncrementalDFABuilder<I> { 027 028 public IncrementalPCDFABuilder(Alphabet<I> inputAlphabet) { 029 super(inputAlphabet); 030 } 031 032 /* 033 * (non-Javadoc) 034 * @see net.automatalib.incremental.dfa.AbstractIncrementalDFABuilder#getState(net.automatalib.words.Word) 035 */ 036 @Override 037 protected State getState(Word<I> word) { 038 State s = init; 039 040 for(I sym : word) { 041 int idx = inputAlphabet.getSymbolIndex(sym); 042 s = s.getSuccessor(idx); 043 if(s == null || s == sink) { 044 return s; 045 } 046 } 047 return s; 048 } 049 050 /* 051 * (non-Javadoc) 052 * @see net.automatalib.incremental.dfa.AbstractIncrementalDFABuilder#lookup(net.automatalib.words.Word) 053 */ 054 @Override 055 public Acceptance lookup(Word<I> word) { 056 State s = getState(word); 057 if(s == null) 058 return Acceptance.DONT_KNOW; 059 return (s != sink) ? s.getAcceptance() : Acceptance.FALSE; 060 } 061 062 /* 063 * (non-Javadoc) 064 * @see net.automatalib.incremental.dfa.AbstractIncrementalDFABuilder#insert(net.automatalib.words.Word, boolean) 065 */ 066 @Override 067 public void insert(Word<I> word, boolean accepting) { 068 int len = word.length(); 069 Acceptance acc = Acceptance.fromBoolean(accepting); 070 071 State curr = init; 072 State conf = null; 073 074 Deque<PathElem> path = new ArrayDeque<>(); 075 076 for(I sym : word) { 077 if(curr == sink || curr.getAcceptance() == Acceptance.FALSE) { 078 if(accepting) { 079 throw new IllegalArgumentException("Conflict"); 080 } 081 if(curr != sink) { 082 purge(curr); 083 } 084 return; 085 } 086 087 if(conf == null && curr.isConfluence()) { 088 conf = curr; 089 } 090 091 int idx = inputAlphabet.getSymbolIndex(sym); 092 093 State succ = curr.getSuccessor(idx); 094 if(succ == null) { 095 break; 096 } 097 path.push(new PathElem(curr, idx)); 098 curr = succ; 099 } 100 101 int prefixLen = path.size(); 102 103 104 State last = curr; 105 106 if(prefixLen == len) { 107 Acceptance currAcc = curr.getAcceptance(); 108 if(currAcc == acc) { 109 return; 110 } 111 if(currAcc != Acceptance.DONT_KNOW) { 112 throw new ConflictException("Incompatible acceptances: " + currAcc + " vs " + acc); 113 } 114 if(!accepting) { 115 purge(last); 116 last = sink; 117 } 118 else { 119 if(conf != null || last.isConfluence()) { 120 last = clone(last, Acceptance.TRUE); 121 } 122 else if(last == init) { 123 updateInitSignature(Acceptance.TRUE); 124 return; 125 } 126 else { 127 last = updateSignature(last, acc); 128 } 129 } 130 } 131 else { 132 if(conf != null) { 133 if(conf == last) { 134 conf = null; 135 } 136 last = hiddenClone(last); 137 } 138 else if(last != init) { 139 hide(last); 140 } 141 142 Word<I> suffix = word.subWord(prefixLen); 143 I sym = suffix.firstSymbol(); 144 int suffTransIdx = inputAlphabet.getSymbolIndex(sym); 145 State suffixState = createSuffix(suffix.subWord(1), accepting); 146 147 if(last != init) { 148 if(accepting) { 149 last = unhide(last, Acceptance.TRUE, suffTransIdx, suffixState); 150 } 151 else { 152 last = unhide(last, suffTransIdx, suffixState); 153 } 154 } 155 else { 156 if(accepting) { 157 updateInitSignature(Acceptance.TRUE, suffTransIdx, suffixState); 158 } 159 else { 160 updateInitSignature(suffTransIdx, suffixState); 161 } 162 } 163 } 164 165 if(path.isEmpty()) { 166 return; 167 } 168 169 if(conf != null) { 170 PathElem next; 171 do { 172 next = path.pop(); 173 State state = next.state; 174 int idx = next.transIdx; 175 if(accepting) { 176 state = clone(state, Acceptance.TRUE, idx, last); 177 } 178 else { 179 state = clone(state, idx, last); 180 } 181 last = state; 182 } while(next.state != conf); 183 } 184 185 186 187 while(path.size() > 1) { 188 PathElem next = path.pop(); 189 State state = next.state; 190 int idx = next.transIdx; 191 State updated; 192 Acceptance oldAcc = state.getAcceptance(); 193 if(accepting) { 194 updated = updateSignature(state, Acceptance.TRUE, idx, last); 195 } 196 else { 197 updated = updateSignature(state, idx, last); 198 } 199 if(state == updated && oldAcc == updated.getAcceptance()) 200 return; 201 last = updated; 202 } 203 204 int finalIdx = path.pop().transIdx; 205 206 if(accepting) { 207 updateInitSignature(Acceptance.TRUE, finalIdx, last); 208 } 209 else { 210 updateInitSignature(finalIdx, last); 211 } 212 } 213 214 /** 215 * Removes a state and all of its successors from the register. 216 * @param state the state to purge 217 */ 218 private void purge(State state) { 219 StateSignature sig = state.getSignature(); 220 if(state.getAcceptance() == Acceptance.TRUE) { 221 throw new IllegalStateException("Attempting to purge accepting state"); 222 } 223 if(register.remove(sig) == null) { 224 return; 225 } 226 sig.acceptance = Acceptance.FALSE; 227 for(int i = 0; i < alphabetSize; i++) { 228 State succ = sig.successors[i]; 229 if(succ != null) { 230 purge(succ); 231 } 232 } 233 } 234 235 /** 236 * Creates a suffix state sequence, i.e., a linear sequence of states connected by transitions 237 * labeled by the letters of the given suffix word. 238 * @param suffix the suffix word 239 * @param acc whether or not the final state should be accepting 240 * @return the first state in the sequence 241 */ 242 private State createSuffix(Word<I> suffix, boolean accepting) { 243 State last; 244 Acceptance intermediate; 245 if(!accepting) { 246 if(sink == null) 247 sink = new State(null); 248 last = sink; 249 intermediate = Acceptance.DONT_KNOW; 250 } 251 else { 252 StateSignature sig = new StateSignature(alphabetSize, Acceptance.TRUE); 253 last = replaceOrRegister(sig); 254 intermediate = Acceptance.TRUE; 255 } 256 257 int len = suffix.length(); 258 for(int i = len - 1; i >= 0; i--) { 259 StateSignature sig = new StateSignature(alphabetSize, intermediate); 260 I sym = suffix.getSymbol(i); 261 int idx = inputAlphabet.getSymbolIndex(sym); 262 sig.successors[idx] = last; 263 last = replaceOrRegister(sig); 264 } 265 266 return last; 267 } 268 269}