295 lines · 9.5 KB · Java
RawDownload
/* * Copyright (c) 1998, 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.text.html.parser; /** * A content model state. This is basically a list of pointers to * the BNF expression representing the model (the ContentModel). * Each element in a DTD has a content model which describes the * elements that may occur inside, and the order in which they can * occur. * <p> * Each time a token is reduced a new state is created. * <p> * See Annex H on page 556 of the SGML handbook for more information. * * @see Parser * @see DTD * @see Element * @see ContentModel * @author Arthur van Hoff */class ContentModelState {    ContentModel model;    long value;    ContentModelState next;     /**     * Create a content model state for a content model.     */    public ContentModelState(ContentModel model) {        this(model, null, 0);    }     /**     * Create a content model state for a content model given the     * remaining state that needs to be reduce.     */    ContentModelState(Object content, ContentModelState next) {        this(content, next, 0);    }     /**     * Create a content model state for a content model given the     * remaining state that needs to be reduce.     */    ContentModelState(Object content, ContentModelState next, long value) {        this.model = (ContentModel)content;        this.next = next;        this.value = value;    }     /**     * Return the content model that is relevant to the current state.     */    public ContentModel getModel() {        ContentModel m = model;        for (int i = 0; i < value; i++) {            if (m.next != null) {                m = m.next;            } else {                return null;            }        }        return m;    }     /**     * 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;    }}