package com.ctc.wstx.dtd;
import java.util.*;
import com.ctc.wstx.util.ExceptionUtil;
import com.ctc.wstx.util.PrefixedName;
Content specification that defines content model that has
multiple alternative elements; including mixed content model.
/**
* Content specification that defines content model that has
* multiple alternative elements; including mixed content model.
*/
public class ChoiceContentSpec
extends ContentSpec
{
final boolean mNsAware;
Whether this is a mixed content model; mostly affects String
representation
/**
* Whether this is a mixed content model; mostly affects String
* representation
*/
final boolean mHasMixed;
final ContentSpec[] mContentSpecs;
/*
///////////////////////////////////////////////////
// Life-cycle
///////////////////////////////////////////////////
*/
private ChoiceContentSpec(boolean nsAware, char arity, boolean mixed,
ContentSpec[] specs)
{
super(arity);
mNsAware = nsAware;
mHasMixed = mixed;
mContentSpecs = specs;
}
private ChoiceContentSpec(boolean nsAware, char arity, boolean mixed, Collection<ContentSpec> specs)
{
super(arity);
mNsAware = nsAware;
mHasMixed = mixed;
mContentSpecs = new ContentSpec[specs.size()];
specs.toArray(mContentSpecs);
}
public static ChoiceContentSpec constructChoice(boolean nsAware, char arity,
Collection<ContentSpec> specs)
{
return new ChoiceContentSpec(nsAware, arity, false, specs);
}
public static ChoiceContentSpec constructMixed(boolean nsAware, Collection<ContentSpec> specs)
{
return new ChoiceContentSpec(nsAware, '*', true, specs);
}
/*
///////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////
*/
@Override
public StructValidator getSimpleValidator()
{
/* Can we create a simple validator? Yes, if the sub-specs are
* all simple (leaves == element tokens with no arity modifier);
* this is always true for mixed.
*/
ContentSpec[] specs = mContentSpecs;
int len = specs.length;
int i;
if (mHasMixed) {
i = len;
} else {
i = 0;
for (; i < len; ++i) {
if (!specs[i].isLeaf()) {
break;
}
}
}
if (i == len) { // all leaves, kewl
PrefixedNameSet keyset = namesetFromSpecs(mNsAware, specs);
return new Validator(mArity, keyset);
}
// Nah, need a DFA...
return null;
}
@Override
public ModelNode rewrite()
{
// First, need to convert sub-specs:
ContentSpec[] specs = mContentSpecs;
int len = specs.length;
ModelNode[] models = new ModelNode[len];
for (int i = 0; i < len; ++i) {
models[i] = specs[i].rewrite();
}
ChoiceModel model = new ChoiceModel(models);
// and then resolve arity modifiers, if necessary:
if (mArity == '*') {
return new StarModel(model);
}
if (mArity == '?') {
return new OptionalModel(model);
}
if (mArity == '+') {
return new ConcatModel(model,
new StarModel(model.cloneModel()));
}
return model;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if (mHasMixed) {
sb.append("(#PCDATA | ");
} else {
sb.append('(');
}
for (int i = 0; i < mContentSpecs.length; ++i) {
if (i > 0) {
sb.append(" | ");
}
sb.append(mContentSpecs[i].toString());
}
sb.append(')');
if (mArity != ' ') {
sb.append(mArity);
}
return sb.toString();
}
/*
///////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////
*/
/*
///////////////////////////////////////////////////
// Package methods
///////////////////////////////////////////////////
*/
protected static PrefixedNameSet namesetFromSpecs(boolean nsAware, ContentSpec[] specs)
{
int len = specs.length;
PrefixedName[] nameArray = new PrefixedName[len];
for (int i = 0; i < len; ++i) {
nameArray[i] = ((TokenContentSpec)specs[i]).getName();
}
if (len < 5) { // 4 or fewer elements -> small
return new SmallPrefixedNameSet(nsAware, nameArray);
}
return new LargePrefixedNameSet(nsAware, nameArray);
}
/*
///////////////////////////////////////////////////
// Validator class that can be used for simple
// choices (including mixed content)
///////////////////////////////////////////////////
*/
final static class Validator
extends StructValidator
{
final char mArity;
final PrefixedNameSet mNames;
int mCount = 0;
public Validator(char arity, PrefixedNameSet names)
{
mArity = arity;
mNames = names;
}
Rules for reuse are simple: if we can have any number of
repetitions, we can just use a shared root instance. Although
its count variable will get updated this doesn't really
matter as it won't be used. Otherwise a new instance has to
be created always, to keep track of instance counts.
/**
* Rules for reuse are simple: if we can have any number of
* repetitions, we can just use a shared root instance. Although
* its count variable will get updated this doesn't really
* matter as it won't be used. Otherwise a new instance has to
* be created always, to keep track of instance counts.
*/
@Override
public StructValidator newInstance() {
return (mArity == '*') ? this : new Validator(mArity, mNames);
}
@Override
public String tryToValidate(PrefixedName elemName)
{
if (!mNames.contains(elemName)) {
if (mNames.hasMultiple()) {
return "Expected one of ("+mNames.toString(" | ")+")";
}
return "Expected <"+mNames.toString("")+">";
}
if (++mCount > 1 && (mArity == '?' || mArity == ' ')) {
if (mNames.hasMultiple()) {
return "Expected $END (already had one of ["
+mNames.toString(" | ")+"]";
}
return "Expected $END (already had one <"
+mNames.toString("")+">]";
}
return null;
}
@Override
public String fullyValid()
{
switch (mArity) {
case '*':
case '?':
return null;
case '+': // need at least one (and multiples checked earlier)
case ' ':
if (mCount > 0) {
return null;
}
return "Expected "+(mArity == '+' ? "at least" : "")
+" one of elements ("+mNames+")";
}
// should never happen:
ExceptionUtil.throwGenericInternal();
return null;
}
}
}