|
What this is
This file is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Other links
The source code
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.xml.dtd.grammar;
import java.util.*;
/**
* Implementation of queriable DTD content models. It is a hungry
* automaton.
*
* @see ContentModelTest
*
* @author Petr Kuzel
*/
abstract class ContentModel {
/**
* Create model by parsing its string representation "|,*?+()WS".
* Caller must filter out ANY , EMPTY and
* (#PCDATA) content models.
*/
public static final ContentModel parseContentModel(String model) {
if (model == null || model.length() == 0) throw new IllegalArgumentException();
PushbackStringTokenizer tokens =
new PushbackStringTokenizer(model, "|,*?+() \t\n", true); // NOI18N
String next = tokens.nextToken();
if (next.charAt(0) != '(' ) throw new IllegalStateException();
return parseContentModel(tokens);
}
private static ContentModel parseContentModel(PushbackStringTokenizer tokens) {
ContentModel model = null;;
List models = new ArrayList(7);
char type = 'E';
char ch;
String next;
do {
next = tokens.nextToken();
ch = next.charAt(0);
if (ch == ' ' || ch == '\t' || ch == '\n') continue;
if (ch == '#') { // #PCDATA
do {
ch = tokens.nextToken().charAt(0);
} while (ch == ' ' || ch == '\t' || ch == '\n');
if (ch != '|') throw new IllegalStateException();
continue;
} else if (ch == '(') {
models.add(parseContentModel(tokens));
} else if (ch == '|') {
type = '|';
} else if (ch == ',') {
type = ',';
} else if (ch == ')') {
break;
} else {
model = new Element(next);
// optional element multiplicity
do {
next = tokens.nextToken();
ch = next.charAt(0);
} while (ch == ' ' || ch == '\t' || ch == '\n');
if (ch == '+') {
model = new MultiplicityGroup(model, 1, -1);
} else if (ch == '?') {
model = new MultiplicityGroup(model, 0, 1);
} else if (ch == '*') {
model = new MultiplicityGroup(model, 0, -1);
} else if (ch == ')') {
// do not pushback!
} else {
tokens.pushback(next);
}
models.add(model);
}
} while (ch != ')');
// create models
if (type == '|') {
model = new Choice((ContentModel[])models.toArray(new ContentModel[0]));
} else if (type == ',') {
model = new Sequence((ContentModel[])models.toArray(new ContentModel[0]));
} else {
// note model contains last Element
}
// determine optional group multiplicity
do {
if (tokens.hasMoreTokens() == false) break;
next = tokens.nextToken();
ch = next.charAt(0);
} while (ch == ' ' || ch == '\t' || ch == '\n');
if (ch == '?') {
model = new MultiplicityGroup(model, 0, 1);
} else if (ch == '*') {
model = new MultiplicityGroup(model, 0, -1);
} else if (ch == '+') {
model = new MultiplicityGroup(model, 1, -1);
} else {
tokens.pushback(next);
}
// System.out.println("Model " + model);
return model;
}
/**
* @return enumeration<String> or null if document is not valid.
*/
public final Enumeration whatCanFollow(Enumeration en) {
reset();
Food food = new Food(en);
if (eat(food)) {
return possibilities();
} else {
return null;
}
}
/**
* Reinitializes the content model to initial state.
*/
protected void reset() {
}
/**
* Move the automaton to next state. It is may not be called
* twice without reset!
* @return true if accepted the food, false at root model indicate document error
*/
protected abstract boolean eat(Food food);
/**
* Enumerate all FIRSTs at current state.
* It must not be called if eat returned false
* @return possible completion
*/
protected abstract Enumeration possibilities();
/**
* Does need the content model a reset because it is in final state?
* @return true if it is in final state
*/
protected boolean terminated() {
return false;
}
/**
* Is the content model in current state optional?
*/
protected boolean isOptional() {
return false;
}
// ~~~~~~~~~~~~~~~~~~~~~~ Implemenation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Single element
*/
private static class Element extends ContentModel {
private final String name;
private boolean full = false;
public Element (String name) {
this.name = name;
}
protected void reset() {
full = false;
}
protected boolean eat(Food food) {
if (food.hasNext() == false) {
return true;
} else {
String next = food.next();
if (this.name.equals(next)) {
full = true;
return true;
} else {
return false;
}
}
}
protected Enumeration possibilities() {
if (terminated() == false) {
return org.openide.util.Enumerations.singleton (name);
} else {
return org.openide.util.Enumerations.empty();
}
}
protected boolean terminated() {
return full;
}
public String toString() {
return "Element[" + name + "]";
}
}
/**
* Mandatory sequence of models.
*/
private static class Sequence extends ContentModel {
private ContentModel[] models;
// index of current model to use <0, models.length>
private int current = 0;
public Sequence(ContentModel[] models) {
this.models = models;
}
/**
* Reset all models upto (inclusive) current one.
*/
protected void reset() {
for (int i = 0; i move all subsequent automatons
// to have accurate possibilities()
int level = food.mark();
for (int i = current + 1; i= max && max > -1) {
return true;
};
peer.reset();
} else if (peer.terminated()) {
// no more food, do not increment current for unterminated
current ++;
}
accept = true;
}
return true;
}
public Enumeration possibilities() {
if (terminated() == false) {
// we force peer reinitialization
if (peer.terminated()) peer.reset();
return peer.possibilities();
} else {
return org.openide.util.Enumerations.empty();
}
}
protected boolean terminated() {
if (current != max) return false;
return peer.terminated();
}
protected boolean isOptional() {
if (min <= current) return true;
return peer.isOptional();
}
public String toString() {
return "MultiplicityGroup[peer=" + peer + ", min=" + min + ", max=" + max + ", current=" + current + "]";
}
}
/**
* At least one sub-content model must eat.
*/
private static class Choice extends ContentModel {
private ContentModel[] models;
private boolean modelsThatNotAcceptedAtLeastOne[];
private boolean terminated = false;
// index of current model to use <0, models.length>
private int current = 0;
public Choice(ContentModel[] models) {
this.models = models;
modelsThatNotAcceptedAtLeastOne = new boolean[models.length];
}
/**
* Reset all models upto (inclusive) current one.
*/
protected void reset() {
for (int i = 0; ihasNext returns null
*/
public String next() {
if (hasNext() == false) {
throw new IllegalStateException();
} else {
String next = (String) list.get(current);
current++;
return next;
}
}
/**
* @return true if it is assured that next is available.
*/
public boolean hasNext() {
if (list.size() > current) return true;
if (en.hasMoreElements()) {
String next = (String) en.nextElement();
return list.add(next);
} else {
return false;
}
}
}
/**
* Partial implementation of single-pushback tokenizer.
*/
private static class PushbackStringTokenizer extends StringTokenizer {
private String pushback = null;
public PushbackStringTokenizer(String tokens, String delim, boolean inc) {
super(tokens, delim, inc);
}
public String nextToken() {
String next;
if (pushback != null) {
next = pushback;
pushback = null;
} else {
next = super.nextToken();
}
return next;
}
public boolean hasMoreTokens() {
if (pushback != null) {
return true;
} else {
return super.hasMoreTokens();
}
}
public void pushback(String pushback) {
if (this.pushback != null) throw new IllegalStateException();
this.pushback = pushback;
}
}
}
|