001 /*
002 * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.text.html.parser;
027
028 /**
029 * A content model state. This is basically a list of pointers to
030 * the BNF expression representing the model (the ContentModel).
031 * Each element in a DTD has a content model which describes the
032 * elements that may occur inside, and the order in which they can
033 * occur.
034 * <p>
035 * Each time a token is reduced a new state is created.
036 * <p>
037 * See Annex H on page 556 of the SGML handbook for more information.
038 *
039 * @see Parser
040 * @see DTD
041 * @see Element
042 * @see ContentModel
043 * @author Arthur van Hoff
044 * @version 1.18 05/05/07
045 */
046 class ContentModelState {
047 ContentModel model;
048 long value;
049 ContentModelState next;
050
051 /**
052 * Create a content model state for a content model.
053 */
054 public ContentModelState(ContentModel model) {
055 this (model, null, 0);
056 }
057
058 /**
059 * Create a content model state for a content model given the
060 * remaining state that needs to be reduce.
061 */
062 ContentModelState(Object content, ContentModelState next) {
063 this (content, next, 0);
064 }
065
066 /**
067 * Create a content model state for a content model given the
068 * remaining state that needs to be reduce.
069 */
070 ContentModelState(Object content, ContentModelState next, long value) {
071 this .model = (ContentModel) content;
072 this .next = next;
073 this .value = value;
074 }
075
076 /**
077 * Return the content model that is relevant to the current state.
078 */
079 public ContentModel getModel() {
080 ContentModel m = model;
081 for (int i = 0; i < value; i++) {
082 if (m.next != null) {
083 m = m.next;
084 } else {
085 return null;
086 }
087 }
088 return m;
089 }
090
091 /**
092 * Check if the state can be terminated. That is there are no more
093 * tokens required in the input stream.
094 * @return true if the model can terminate without further input
095 */
096 public boolean terminate() {
097 switch (model.type) {
098 case '+':
099 if ((value == 0) && !(model).empty()) {
100 return false;
101 }
102 case '*':
103 case '?':
104 return (next == null) || next.terminate();
105
106 case '|':
107 for (ContentModel m = (ContentModel) model.content; m != null; m = m.next) {
108 if (m.empty()) {
109 return (next == null) || next.terminate();
110 }
111 }
112 return false;
113
114 case '&': {
115 ContentModel m = (ContentModel) model.content;
116
117 for (int i = 0; m != null; i++, m = m.next) {
118 if ((value & (1L << i)) == 0) {
119 if (!m.empty()) {
120 return false;
121 }
122 }
123 }
124 return (next == null) || next.terminate();
125 }
126
127 case ',': {
128 ContentModel m = (ContentModel) model.content;
129 for (int i = 0; i < value; i++, m = m.next)
130 ;
131
132 for (; (m != null) && m.empty(); m = m.next)
133 ;
134 if (m != null) {
135 return false;
136 }
137 return (next == null) || next.terminate();
138 }
139
140 default:
141 return false;
142 }
143 }
144
145 /**
146 * Check if the state can be terminated. That is there are no more
147 * tokens required in the input stream.
148 * @return the only possible element that can occur next
149 */
150 public Element first() {
151 switch (model.type) {
152 case '*':
153 case '?':
154 case '|':
155 case '&':
156 return null;
157
158 case '+':
159 return model.first();
160
161 case ',': {
162 ContentModel m = (ContentModel) model.content;
163 for (int i = 0; i < value; i++, m = m.next)
164 ;
165 return m.first();
166 }
167
168 default:
169 return model.first();
170 }
171 }
172
173 /**
174 * Advance this state to a new state. An exception is thrown if the
175 * token is illegal at this point in the content model.
176 * @return next state after reducing a token
177 */
178 public ContentModelState advance(Object token) {
179 switch (model.type) {
180 case '+':
181 if (model.first(token)) {
182 return new ContentModelState(model.content,
183 new ContentModelState(model, next, value + 1))
184 .advance(token);
185 }
186 if (value != 0) {
187 if (next != null) {
188 return next.advance(token);
189 } else {
190 return null;
191 }
192 }
193 break;
194
195 case '*':
196 if (model.first(token)) {
197 return new ContentModelState(model.content, this )
198 .advance(token);
199 }
200 if (next != null) {
201 return next.advance(token);
202 } else {
203 return null;
204 }
205
206 case '?':
207 if (model.first(token)) {
208 return new ContentModelState(model.content, next)
209 .advance(token);
210 }
211 if (next != null) {
212 return next.advance(token);
213 } else {
214 return null;
215 }
216
217 case '|':
218 for (ContentModel m = (ContentModel) model.content; m != null; m = m.next) {
219 if (m.first(token)) {
220 return new ContentModelState(m, next)
221 .advance(token);
222 }
223 }
224 break;
225
226 case ',': {
227 ContentModel m = (ContentModel) model.content;
228 for (int i = 0; i < value; i++, m = m.next)
229 ;
230
231 if (m.first(token) || m.empty()) {
232 if (m.next == null) {
233 return new ContentModelState(m, next)
234 .advance(token);
235 } else {
236 return new ContentModelState(m,
237 new ContentModelState(model, next,
238 value + 1)).advance(token);
239 }
240 }
241 break;
242 }
243
244 case '&': {
245 ContentModel m = (ContentModel) model.content;
246 boolean complete = true;
247
248 for (int i = 0; m != null; i++, m = m.next) {
249 if ((value & (1L << i)) == 0) {
250 if (m.first(token)) {
251 return new ContentModelState(m,
252 new ContentModelState(model, next,
253 value | (1L << i)))
254 .advance(token);
255 }
256 if (!m.empty()) {
257 complete = false;
258 }
259 }
260 }
261 if (complete) {
262 if (next != null) {
263 return next.advance(token);
264 } else {
265 return null;
266 }
267 }
268 break;
269 }
270
271 default:
272 if (model.content == token) {
273 if (next == null && (token instanceof Element)
274 && ((Element) token).content != null) {
275 return new ContentModelState(
276 ((Element) token).content);
277 }
278 return next;
279 }
280 // PENDING: Currently we don't correctly deal with optional start
281 // tags. This can most notably be seen with the 4.01 spec where
282 // TBODY's start and end tags are optional.
283 // Uncommenting this and the PENDING in ContentModel will
284 // correctly skip the omit tags, but the delegate is not notified.
285 // Some additional API needs to be added to track skipped tags,
286 // and this can then be added back.
287 /*
288 if ((model.content instanceof Element)) {
289 Element e = (Element)model.content;
290
291 if (e.omitStart() && e.content != null) {
292 return new ContentModelState(e.content, next).advance(
293 token);
294 }
295 }
296 */
297 }
298
299 // We used to throw this exception at this point. However, it
300 // was determined that throwing this exception was more expensive
301 // than returning null, and we could not justify to ourselves why
302 // it was necessary to throw an exception, rather than simply
303 // returning null. I'm leaving it in a commented out state so
304 // that it can be easily restored if the situation ever arises.
305 //
306 // throw new IllegalArgumentException("invalid token: " + token);
307 return null;
308 }
309 }
|