/*
* 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 + "]";
}
}