ContentModelState.java revision 2362
286N/A/*
286N/A * Copyright (c) 1998, 2000, Oracle and/or its affiliates. All rights reserved.
286N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
286N/A *
286N/A * This code is free software; you can redistribute it and/or modify it
286N/A * under the terms of the GNU General Public License version 2 only, as
286N/A * published by the Free Software Foundation. Oracle designates this
286N/A * particular file as subject to the "Classpath" exception as provided
286N/A * by Oracle in the LICENSE file that accompanied this code.
286N/A *
286N/A * This code is distributed in the hope that it will be useful, but WITHOUT
286N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
286N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
286N/A * version 2 for more details (a copy is included in the LICENSE file that
286N/A * accompanied this code).
286N/A *
286N/A * You should have received a copy of the GNU General Public License version
286N/A * 2 along with this work; if not, write to the Free Software Foundation,
286N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
286N/A *
286N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
286N/A * or visit www.oracle.com if you need additional information or have any
286N/A * questions.
286N/A */
286N/A
286N/Apackage javax.swing.text.html.parser;
286N/A
286N/A/**
286N/A * A content model state. This is basically a list of pointers to
286N/A * the BNF expression representing the model (the ContentModel).
286N/A * Each element in a DTD has a content model which describes the
286N/A * elements that may occur inside, and the order in which they can
286N/A * occur.
286N/A * <p>
286N/A * Each time a token is reduced a new state is created.
286N/A * <p>
286N/A * See Annex H on page 556 of the SGML handbook for more information.
286N/A *
286N/A * @see Parser
286N/A * @see DTD
286N/A * @see Element
286N/A * @see ContentModel
286N/A * @author Arthur van Hoff
286N/A */
286N/Aclass ContentModelState {
286N/A ContentModel model;
286N/A long value;
286N/A ContentModelState next;
286N/A
286N/A /**
286N/A * Create a content model state for a content model.
286N/A */
286N/A public ContentModelState(ContentModel model) {
286N/A this(model, null, 0);
286N/A }
286N/A
286N/A /**
286N/A * Create a content model state for a content model given the
286N/A * remaining state that needs to be reduce.
286N/A */
286N/A ContentModelState(Object content, ContentModelState next) {
286N/A this(content, next, 0);
286N/A }
286N/A
286N/A /**
286N/A * Create a content model state for a content model given the
286N/A * remaining state that needs to be reduce.
286N/A */
286N/A ContentModelState(Object content, ContentModelState next, long value) {
286N/A this.model = (ContentModel)content;
286N/A this.next = next;
286N/A this.value = value;
286N/A }
286N/A
286N/A /**
286N/A * Return the content model that is relevant to the current state.
286N/A */
286N/A public ContentModel getModel() {
286N/A ContentModel m = model;
286N/A for (int i = 0; i < value; i++) {
286N/A if (m.next != null) {
286N/A m = m.next;
286N/A } else {
286N/A return null;
286N/A }
286N/A }
286N/A return m;
286N/A }
/**
* Check if the state can be terminated. That is there are no more
* tokens required in the input stream.
* @return true if the model can terminate without further input
*/
public boolean terminate() {
switch (model.type) {
case '+':
if ((value == 0) && !(model).empty()) {
return false;
}
case '*':
case '?':
return (next == null) || next.terminate();
case '|':
for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
if (m.empty()) {
return (next == null) || next.terminate();
}
}
return false;
case '&': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; m != null ; i++, m = m.next) {
if ((value & (1L << i)) == 0) {
if (!m.empty()) {
return false;
}
}
}
return (next == null) || next.terminate();
}
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
for (; (m != null) && m.empty() ; m = m.next);
if (m != null) {
return false;
}
return (next == null) || next.terminate();
}
default:
return false;
}
}
/**
* Check if the state can be terminated. That is there are no more
* tokens required in the input stream.
* @return the only possible element that can occur next
*/
public Element first() {
switch (model.type) {
case '*':
case '?':
case '|':
case '&':
return null;
case '+':
return model.first();
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
return m.first();
}
default:
return model.first();
}
}
/**
* Advance this state to a new state. An exception is thrown if the
* token is illegal at this point in the content model.
* @return next state after reducing a token
*/
public ContentModelState advance(Object token) {
switch (model.type) {
case '+':
if (model.first(token)) {
return new ContentModelState(model.content,
new ContentModelState(model, next, value + 1)).advance(token);
}
if (value != 0) {
if (next != null) {
return next.advance(token);
} else {
return null;
}
}
break;
case '*':
if (model.first(token)) {
return new ContentModelState(model.content, this).advance(token);
}
if (next != null) {
return next.advance(token);
} else {
return null;
}
case '?':
if (model.first(token)) {
return new ContentModelState(model.content, next).advance(token);
}
if (next != null) {
return next.advance(token);
} else {
return null;
}
case '|':
for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
if (m.first(token)) {
return new ContentModelState(m, next).advance(token);
}
}
break;
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
if (m.first(token) || m.empty()) {
if (m.next == null) {
return new ContentModelState(m, next).advance(token);
} else {
return new ContentModelState(m,
new ContentModelState(model, next, value + 1)).advance(token);
}
}
break;
}
case '&': {
ContentModel m = (ContentModel)model.content;
boolean complete = true;
for (int i = 0 ; m != null ; i++, m = m.next) {
if ((value & (1L << i)) == 0) {
if (m.first(token)) {
return new ContentModelState(m,
new ContentModelState(model, next, value | (1L << i))).advance(token);
}
if (!m.empty()) {
complete = false;
}
}
}
if (complete) {
if (next != null) {
return next.advance(token);
} else {
return null;
}
}
break;
}
default:
if (model.content == token) {
if (next == null && (token instanceof Element) &&
((Element)token).content != null) {
return new ContentModelState(((Element)token).content);
}
return next;
}
// PENDING: Currently we don't correctly deal with optional start
// tags. This can most notably be seen with the 4.01 spec where
// TBODY's start and end tags are optional.
// Uncommenting this and the PENDING in ContentModel will
// correctly skip the omit tags, but the delegate is not notified.
// Some additional API needs to be added to track skipped tags,
// and this can then be added back.
/*
if ((model.content instanceof Element)) {
Element e = (Element)model.content;
if (e.omitStart() && e.content != null) {
return new ContentModelState(e.content, next).advance(
token);
}
}
*/
}
// We used to throw this exception at this point. However, it
// was determined that throwing this exception was more expensive
// than returning null, and we could not justify to ourselves why
// it was necessary to throw an exception, rather than simply
// returning null. I'm leaving it in a commented out state so
// that it can be easily restored if the situation ever arises.
//
// throw new IllegalArgumentException("invalid token: " + token);
return null;
}
}