/* Woodstox XML processor
*
* Copyright (c) 2004 Tatu Saloranta, tatu.saloranta@iki.fi
*
* Licensed under the License specified in the file LICENSE which is
* included with the source code.
* You may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ctc.wstx.dtd;
import java.util.*;
import com.ctc.wstx.util.PrefixedName;
Class that represents a state in DFA used for validating complex
DTD content models.
/**
* Class that represents a state in DFA used for validating complex
* DTD content models.
*/
public final class DFAState
{
final int mIndex;
final boolean mAccepting;
BitSet mTokenSet;
HashMap<PrefixedName,DFAState> mNext = new HashMap<PrefixedName,DFAState>();
/*
///////////////////////////////////////////////
// Life-cycle:
///////////////////////////////////////////////
*/
public DFAState(int index, BitSet tokenSet)
{
mIndex = index;
// If we have a transition to state 0, it is an accepting state...
mAccepting = tokenSet.get(0);
mTokenSet = tokenSet;
}
public static DFAState constructDFA(ContentSpec rootSpec)
{
// Let's first create the real model tree:
ModelNode modelRoot = rootSpec.rewrite();
/* Then we need to add the dummy end token, and concat node
* to contain it:
*/
TokenModel eofToken = TokenModel.getNullToken();
ConcatModel dummyRoot = new ConcatModel(modelRoot, eofToken);
/* then need to allocate index numbers for tokens
* (which will also calculate nullability)
*/
ArrayList<TokenModel> tokens = new ArrayList<TokenModel>();
tokens.add(eofToken); // has to be added first, explicitly
dummyRoot.indexTokens(tokens);
/* And then we can request calculation of follow pos; this will
* also recursively calculate first/last pos as needed:
*/
int flen = tokens.size();
BitSet[] followPos = new BitSet[flen];
PrefixedName[] tokenNames = new PrefixedName[flen];
for (int i = 0; i < flen; ++i) {
followPos[i] = new BitSet(flen);
tokenNames[i] = tokens.get(i).getName();
}
dummyRoot.calcFollowPos(followPos);
/* And then we can calculate DFA stuff. First step is to get
* firstpos set for the root node, for creating the first
* state:
*/
BitSet initial = new BitSet(flen);
dummyRoot.addFirstPos(initial);
DFAState firstState = new DFAState(0, initial);
ArrayList<DFAState> stateList = new ArrayList<DFAState>();
stateList.add(firstState);
HashMap<BitSet,DFAState> stateMap = new HashMap<BitSet,DFAState>();
stateMap.put(initial, firstState);
int i = 0;
while (i < stateList.size()) {
DFAState curr = stateList.get(i++);
curr.calcNext(tokenNames, followPos, stateList, stateMap);
}
// DEBUG:
/*
for (i = 0; i < stateList.size(); ++i) {
//System.out.println(stateList.get(i));
}
*/
// And there we have it!
return firstState;
}
/*
///////////////////////////////////////////////
// Public API, accessors:
///////////////////////////////////////////////
*/
public boolean isAcceptingState() {
return mAccepting;
}
public int getIndex() {
return mIndex;
}
public DFAState findNext(PrefixedName elemName) {
return mNext.get(elemName);
}
public TreeSet<PrefixedName> getNextNames() {
// Let's order them alphabetically
TreeSet<PrefixedName> names = new TreeSet<PrefixedName>();
for (PrefixedName n : mNext.keySet()) {
names.add(n);
}
return names;
}
public void calcNext(PrefixedName[] tokenNames, BitSet[] tokenFPs,
List<DFAState> stateList, Map<BitSet,DFAState> stateMap)
{
/* Need to loop over all included tokens, and find groups
* of said tokens
*/
int first = -1;
/* Need to clone; can not modify in place, since the BitSet
* is also used as the key...
*/
BitSet tokenSet = (BitSet) mTokenSet.clone();
// No need to keep the reference to it, though:
mTokenSet = null;
while ((first = tokenSet.nextSetBit(first+1)) >= 0) {
PrefixedName tokenName = tokenNames[first];
/* Special case; the dummy end token has null as name;
* we can skip that one:
*/
if (tokenName == null) {
continue;
}
BitSet nextGroup = (BitSet) tokenFPs[first].clone();
int second = first;
while ((second = tokenSet.nextSetBit(second+1)) > 0) {
if (tokenNames[second] == tokenName) {
// Let's clear it, too, so we won't match it again:
tokenSet.clear(second);
nextGroup.or(tokenFPs[second]);
}
}
// Ok; is it a new group?
DFAState next = stateMap.get(nextGroup);
if (next == null) { // yup!
next = new DFAState(stateList.size(), nextGroup);
stateList.add(next);
stateMap.put(nextGroup, next);
}
mNext.put(tokenName, next);
}
}
/*
///////////////////////////////////////////////
// Other methods
///////////////////////////////////////////////
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("State #"+mIndex+":\n");
sb.append(" Accepting: "+mAccepting);
sb.append("\n Next states:\n");
for (Map.Entry<PrefixedName,DFAState> en : mNext.entrySet()) {
sb.append(en.getKey());
sb.append(" -> ");
DFAState next = en.getValue();
sb.append(next.getIndex());
sb.append("\n");
}
return sb.toString();
}
}