001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojava.bio.program.tagvalue; 023 024import java.util.HashMap; 025import java.util.Map; 026 027import org.biojava.utils.ParserException; 028 029/** 030 * This class implements a state machine for parsing events from 031 * the Parser class. 032 * </p> 033 * <p> 034 * Each State can be specified to deliver events 035 * to a particular TagValueListener. 036 * </p> 037 * <p> 038 * Transitions can be specified to occur between States when 039 * specific events are encountered. These events can be 040 * Tags delivered by startTag as well as the endTag/endRecord 041 * events. Events that result in exit from the current State 042 * can be specified to be notifiable to the State listener. 043 * </p> 044 * <p> 045 * In addition, tables of transitions can be defined and specified 046 * as the fallback when the a corresponding Transition cannot be 047 * found for the tag. This is useful for specifying the destination 048 * States that are globally applicable for particular tags. As the 049 * fallbacks can be chained, you can end up with a hierarchy of 050 * Transition Tables, some being State-specific, others applicable to 051 * groups of States and finally one being global. 052 * </p> 053 * @author David Huen 054 */ 055public class StateMachine 056 implements TagValueWrapper 057{ 058 protected static final String START_RECORD_TAG = "__START_RECORD_TAG__"; 059 protected static final String END_RECORD_TAG = "__END_RECORD__"; 060 protected static final String END_TAG = "__END_TAG__"; 061 protected static final String MAGICAL_STATE = "__MAGICAL__"; 062 063 protected TagValueListener delegate = null; 064 065 /** 066 * Interface implemented by State listeners that 067 * want notification when a transition leaves the State. 068 */ 069 public interface ExitNotification 070 { 071 public void notifyExit() throws ParserException; 072 } 073 074 /** 075 * Interface for a State within this StateMachine 076 */ 077 public interface State 078 { 079 public String getLabel(); 080 public TagValueListener getListener(); 081// public void setListener(TagValueListener listener); 082// public void setTransition(Object tag, State destination); 083// public void setDefaultTransition(State defaultDestination); 084 public void transit(Object tag) throws ParserException; 085 } 086 087 /** 088 * class to represent a State Transition 089 */ 090 public class Transition 091 { 092 /** 093 * the terminus of this Transition 094 */ 095 public State destination = null; 096 097 /** 098 * should the State listener be notified 099 * that this transition is leaving the state. 100 */ 101 public boolean notifyOnExit = false; 102 103 private Transition(State destination, boolean notifyOnExit) 104 { 105 this.destination = destination; 106 this.notifyOnExit = notifyOnExit; 107 } 108 } 109 110 /** 111 * Table of Transition destination States 112 * and their corresponding Tags. 113 * <p> 114 * Note that you can chain a series of these 115 * Transition tables and the lookup will 116 * proceed along the chain until it succeeds. 117 */ 118 public class TransitionTable 119 { 120 private Map table = new HashMap(); 121 private TransitionTable fallback = null; 122 123 protected TransitionTable() {} 124 125 /** 126 * set a Transition within this TransitionTable (2-argument form) 127 */ 128 public void put(Object tag, Transition transition) 129 throws ParserException 130 { 131 if (table.containsKey(tag)) 132 throw new ParserException("duplicate tag " + tag); 133 134 table.put(tag, transition); 135 } 136 137 /** 138 * set a Transition within this TransitionTable (3-argument form) 139 */ 140 public void setTransition(Object tag, State destination, boolean notifyOnExit) 141 throws ParserException 142 { 143 put(tag, new Transition(destination, notifyOnExit)); 144 } 145 146 /** 147 * get the Transition associated with the specified tag. 148 */ 149 public Transition get(Object tag) 150 { 151 Transition transition = (Transition) table.get(tag); 152 153 if ((transition == null) && (fallback != null)) { 154 // do a fallback lookup 155 transition = fallback.get(tag); 156 } 157 158 return transition; 159 } 160 161 /** 162 * set the specified TransitionTable to be looked 163 * looked up if the Transition cannot be found in 164 * this one. 165 */ 166 public void setFallback(TransitionTable fallback) 167 { 168 this.fallback = fallback; 169 } 170 } 171 172 173 /** 174 * Implementation of a State in a state machine 175 */ 176 public class BasicState 177 implements State 178 { 179 private String label; 180 private TransitionTable transitions; 181 private TransitionTable defaultTransitions; 182 private TagValueListener listener = null; 183 184 /** 185 * This is the default constructor 186 */ 187 public BasicState(String label) 188 { 189 this.label = label; 190 this.transitions = new TransitionTable(); 191 } 192 193 /** 194 * when this constructor is used, a fixed listener 195 * is used with this state. When setListener is called 196 * on this class, it is the listener of this fixed 197 * listener that is changed. 198 */ 199 public BasicState(String label, TagValueListener listener) 200 { 201 this.label = label; 202 this.listener = listener; 203 this.transitions = new TransitionTable(); 204 } 205 206 /** 207 * return the label of this class. 208 */ 209 public String getLabel() { return label; } 210 211 /** 212 * return the TagValueListener assigned to this State. 213 */ 214 public TagValueListener getListener() { return listener; } 215 216 /** 217 * set a TagValueListener for this State. 218 */ 219 public void setListener(TagValueListener listener) 220 { 221 this.listener = listener; 222 } 223 224 /** 225 * set a Transition for this State 226 */ 227 public void setTransition(Object tag, State destination, boolean notifyOnExit) 228 throws ParserException 229 { 230 transitions.setTransition(tag, destination, notifyOnExit); 231 } 232 233 /** 234 * set a Transition for this State setting notifyOnExit to false. 235 */ 236 public void setTransition(Object tag, State destination) 237 throws ParserException 238 { 239 setTransition(tag, destination, false); 240 } 241 242 /** 243 * retrieve the TransitionTable for this State. 244 */ 245 public TransitionTable getTransitionTable() 246 { 247 return transitions; 248 } 249 250 /** 251 * specify fallback TransitionTable for this State 252 */ 253 public void setDefaultTransitions(TransitionTable defaultTransitions) 254 { 255 this.defaultTransitions = defaultTransitions; 256 } 257 258 /** 259 * Find the destination State when the specified tag 260 * is encountered. If the destination is successfully 261 * obtained, the listener is also notified of exit if 262 * required. 263 */ 264 public void transit(Object tag) 265 throws ParserException 266 { 267 268 Transition transition = transitions.get(tag); 269 270 if (transition == null) { 271 // explicit transition unavailable. 272 // Use fallback default. 273 if (defaultTransitions == null) 274 throw new ParserException("no transition available from " + statePointer.getLabel() + " for tag '" + tag + "'"); 275 276 transition = defaultTransitions.get(tag); 277 278 if (transition == null) 279 throw new ParserException("no transition available from " + statePointer.getLabel() + " for tag '" + tag + "'"); 280 } 281 282 State nextState = transition.destination; 283 284 if (transition.notifyOnExit) { 285// System.out.println("notifyOnExit being checked. this is " + this.getLabel() + " next is " + nextState.getLabel()); 286 if (nextState != this) { 287 if (listener instanceof ExitNotification) { 288 ((ExitNotification) listener).notifyExit(); 289 } 290 } 291 } 292 293 statePointer = nextState; 294 } 295 } 296 297 /** 298 * a basic listener for a State. It forwards all events to the 299 * delegate for the StateMachine. Extend to implement listeners 300 * for specific states. 301 */ 302 public class SimpleStateListener 303 implements TagValueListener 304 { 305 private boolean exceptionOnNullDelegate = true; 306 307 /** 308 * determines if an exception is thrown when an event 309 * arrives without the delegate being set. Default 310 * is that a ParserException is thrown. 311 i*/ 312 public void setExceptionOnNullDelegate(boolean throwException) 313 { exceptionOnNullDelegate = throwException; } 314 315 public void startTag(Object tag) 316 throws ParserException 317 { 318 if (delegate != null) 319 delegate.startTag(tag); 320 else if (exceptionOnNullDelegate) 321 throw new ParserException("event arrived without a delegate being specified"); 322 } 323 324 public void endTag() 325 throws ParserException 326 { 327 if (delegate != null) 328 delegate.endTag(); 329 else if (exceptionOnNullDelegate) 330 throw new ParserException("event arrived without a delegate being specified"); 331 } 332 333 public void startRecord() 334 throws ParserException 335 { 336 if (delegate != null) 337 delegate.startRecord(); 338 else if (exceptionOnNullDelegate) 339 throw new ParserException("event arrived without a delegate being specified"); 340 } 341 342 public void endRecord() 343 throws ParserException 344 { 345 if (delegate != null) 346 delegate.endRecord(); 347 else if (exceptionOnNullDelegate) 348 throw new ParserException("event arrived without a delegate being specified"); 349 } 350 351 public void value(TagValueContext ctxt, java.lang.Object value) 352 throws ParserException 353 { 354 if (delegate != null) 355 delegate.value(ctxt, value); 356 else if (exceptionOnNullDelegate) 357 throw new ParserException("event arrived without a delegate being specified"); 358 } 359 } 360 361 362 /** 363 * current state 364 */ 365 private State statePointer; 366 367 /** 368 * mapping between state labels and states 369 */ 370 private Map states = new HashMap(); 371 private BasicState magicalState; 372 373 public StateMachine() 374 { 375 magicalState = createState(MAGICAL_STATE); 376 statePointer = magicalState; 377 } 378 379 public BasicState getMagicalState() { return magicalState; } 380 381 public BasicState createState(String label) 382 { 383 if (states.get(label)!= null) { 384 throw new IllegalArgumentException("label " + label + " is not unique"); 385 } 386 387 BasicState newState = new BasicState(label); 388 states.put(label, newState); 389 390 return newState; 391 } 392 393 public State getState(String label) 394 { 395 return (State) states.get(label); 396 } 397 398 public TransitionTable createTransitionTable() 399 { 400 return new TransitionTable(); 401 } 402 403 /** 404 * TagValueWrapper interface 405 */ 406 public void setDelegate(TagValueListener delegate) { this.delegate = delegate; } 407 public TagValueListener getDelegate() { return delegate; } 408 409 /** 410 * TagValueListener interface 411 */ 412 public void startTag(Object tag) 413 throws ParserException 414 { 415 statePointer.transit(tag); 416 417 TagValueListener listener = statePointer.getListener(); 418 if (listener != null) listener.startTag(tag); 419 } 420 421 public void endTag() 422 throws ParserException 423 { 424 TagValueListener lstnr; 425 if ((lstnr = ((BasicState) statePointer).getListener()) != null) 426 lstnr.endTag(); 427 statePointer.transit(END_TAG); 428 } 429 430 public void startRecord() 431 throws ParserException 432 { 433 TagValueListener lstnr; 434 if ((lstnr = ((BasicState) statePointer).getListener()) != null) 435 lstnr.startRecord(); 436 statePointer.transit(START_RECORD_TAG); 437 } 438 439 public void endRecord() 440 throws ParserException 441 { 442 TagValueListener lstnr; 443 if ((lstnr = ((BasicState) statePointer).getListener()) != null) 444 lstnr.endRecord(); 445 statePointer.transit(END_RECORD_TAG); 446 } 447 448 public void value(TagValueContext ctxt, Object value) 449 throws ParserException 450 { 451 TagValueListener lstnr; 452 if ((lstnr = ((BasicState) statePointer).getListener()) != null) 453 lstnr.value(ctxt, value); 454 } 455}