/* * Reference ETL Parser for Java * Copyright (c) 2000-2009 Constantine A Plotnikov * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.sf.etl.parsers.internal.term_parser.states; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import net.sf.etl.parsers.PhraseTokens; import net.sf.etl.parsers.TokenKey; import net.sf.etl.parsers.internal.term_parser.states.buildtime.NopState; /** * This class encapsulate management of look ahead information. Each activation * in the grammar has look-ahead declaration. * * The class is not thread safe. * * @author const */ public class LookAheadSet { /** All tokens value */ private static final String ALL_TOKENS = null; /** if true, the lookahead info is frozen */ private boolean isFrozen; /** if true this info is supposed to contain empty */ private boolean constainsEmpty; /** Phrase tokens */ private final Set<PhraseTokens> phraseTokens = new LinkedHashSet<PhraseTokens>(); /** Map from token to text */ private final Map<TokenKey, Set<String>> tokenToTextSet = new LinkedHashMap<TokenKey, Set<String>>(); /** * This constructor creates new lookahead info by copying data from previous * one * * @param other * other lookahead info */ public LookAheadSet(LookAheadSet other) { addAll(other); } /** * A constructor that starts with empty set. */ public LookAheadSet() { } /** * Check if this lookahead info is still modifiable */ private void checkModifiable() { if (isFrozen) { throw new IllegalStateException( "Look ahead information has been frozen"); } } /** * Get instance of lookahead info for phrase token * * @param token * a phrase token for lookahead * @return lookahead information */ public static LookAheadSet get(PhraseTokens token) { final LookAheadSet rc = new LookAheadSet(); rc.add(token); return rc; } /** * @return true if set contains empty */ public boolean containsEmpty() { return constainsEmpty; } /** * Add empty sequence to lookahead */ public void addEmpty() { checkModifiable(); constainsEmpty = true; } /** * @return LookAheadInfo that contains only empty value */ public static LookAheadSet getWithEmpty() { final LookAheadSet rc = new LookAheadSet(); rc.addEmpty(); return rc; } /** * @param tokenKey * a token kind * @return get look ahead info that matches token of the specified kind */ public static LookAheadSet get(TokenKey tokenKey) { return getWithText(tokenKey, ALL_TOKENS); } /** * @param tokenKey * token kind * @param text * a text to match * @return get lookahead info that matches specific special */ public static LookAheadSet getWithText(TokenKey tokenKey, String text) { final LookAheadSet rc = new LookAheadSet(); rc.addToken(tokenKey, text); return rc; } /** * Add token to match * * @param tokenKey * a token kind to match * @param text * a text to match */ private void addToken(TokenKey tokenKey, String text) { checkModifiable(); Set<String> textSet = tokenToTextSet.get(tokenKey); if (textSet == null) { textSet = new LinkedHashSet<String>(); tokenToTextSet.put(tokenKey, textSet); } textSet.add(text); } /** * Add all values from additional lookahead info * * @param other * other lookahead info */ public void addAll(LookAheadSet other) { checkModifiable(); // add empty constainsEmpty |= other.constainsEmpty; // add phrase tokens phraseTokens.addAll(other.phraseTokens); // add to map for (final Map.Entry<TokenKey, Set<String>> j_e : other.tokenToTextSet .entrySet()) { Set<String> thisTextSet = tokenToTextSet.get(j_e.getKey()); final Set<String> otherTextSet = j_e.getValue(); if (thisTextSet == null) { thisTextSet = new LinkedHashSet<String>(); tokenToTextSet.put(j_e.getKey(), thisTextSet); } thisTextSet.addAll(otherTextSet); } } /** * Check if two look ahead info are conflicts. * * @param other * info object to compare with * @return a string that enumerates some found problems. */ public String conflictsWith(LookAheadSet other) { // compare empty indicators, there is conflict if both contains empty if (this.constainsEmpty && other.constainsEmpty) { return "*EMPTY*"; } // Compare phrase tokens. There should be no intersections. final HashSet<PhraseTokens> phraseTokensTmp = new HashSet<PhraseTokens>( this.phraseTokens); phraseTokensTmp.retainAll(other.phraseTokens); if (!phraseTokensTmp.isEmpty()) { return phraseTokensTmp.toString(); } // Compare tokens. final LinkedHashSet<TokenKey> tokensTmp = new LinkedHashSet<TokenKey>( this.tokenToTextSet.keySet()); tokensTmp.retainAll(other.tokenToTextSet.keySet()); for (final TokenKey tokenKey : tokensTmp) { final Set<String> thisTextSet = tokenToTextSet.get(tokenKey); final Set<String> otherTextSet = other.tokenToTextSet.get(tokenKey); final HashSet<String> textTmp = new HashSet<String>(thisTextSet); textTmp.retainAll(otherTextSet); if (!textTmp.isEmpty()) { return tokenKey + ":" + textTmp.toString(); } } return null; } /** * Add phrase token * * @param token * a token to add */ public void add(PhraseTokens token) { checkModifiable(); phraseTokens.add(token); } /** * Remove empty from set */ public void removeEmpty() { checkModifiable(); constainsEmpty = false; } /** * Check if set contains phrase token * * @param token * a token to check * @return true if look ahead contains token */ public boolean contains(PhraseTokens token) { return phraseTokens.contains(token); } /** * Update choice node to refer to the specified optionState for tokens in * this lookahead state. * * * The method also assumes that all conflicts are eliminated * * The method assumes that start choice node points to NOP state. This is * node is used for redirection if this set contains "empty", alternative * state of this choice node is changed to point to option state too. This * is done because it is assumed that option will refer to continuation and * will possible create some objects on the way even if it matches no tokens * on the path. * * @param start * start of choice node * @param optionState * a state that is associated with this lookahead */ public void buildChoiceStates(PhraseKindChoice start, State optionState) { if (optionState == null) { throw new NullPointerException( "Option state cannot be null for look ahead " + this); } // get exit state final NopState exit = (NopState) start.getFallbackState(); // process empty state if (containsEmpty()) { exit.setNextState(optionState); } // process phrase states for (final Iterator<PhraseTokens> i = phraseTokens.iterator(); i .hasNext();) { final PhraseTokens kind = i.next(); start.onKind(kind, optionState); } // process tokens TokenKeyChoice tokenChoice = (TokenKeyChoice) start .getStateByKind(PhraseTokens.SIGNIFICANT); if (tokenChoice == null) { tokenChoice = new TokenKeyChoice(exit); start.onKind(PhraseTokens.SIGNIFICANT, tokenChoice); } for (final Map.Entry<TokenKey, Set<String>> e : tokenToTextSet .entrySet()) { final TokenKey token = e.getKey(); if (token == null) { tokenChoice.setFallbackState(optionState); } else { final Set<String> textSet = e.getValue(); TokenTextChoice textChoice = (TokenTextChoice) tokenChoice .getStateByKind(token); if (textChoice == null) { textChoice = new TokenTextChoice(exit); tokenChoice.onKey(token, textChoice); } buildTextChoiceState(textChoice, textSet, optionState); } } } /** * Build text choice state using text set sets * * @param textChoice * a choice node to build * @param textSet * a set of texts * @param optionState * target state */ private static void buildTextChoiceState(TokenTextChoice textChoice, Set<String> textSet, State optionState) { for (final String text : textSet) { if (text == ALL_TOKENS) { textChoice.setFallbackState(optionState); } else { textChoice.onText(text, optionState); } } } /** * @see java.lang.Object#toString() */ @Override public String toString() { // NOTE POST 0.2: this value is used in syntax error reporting. Make it // more friendly. return "LA[" + (containsEmpty() ? "*Empty*," : "") + "phraseTokens=" + phraseTokens + ";tokens=" + tokenToTextSet + "]"; } }