1 /* 2 * Copyright (c) 1998, 2000, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.text.html.parser; 27 28 /** 29 * A content model state. This is basically a list of pointers to 30 * the BNF expression representing the model (the ContentModel). 31 * Each element in a DTD has a content model which describes the 32 * elements that may occur inside, and the order in which they can 33 * occur. 34 * <p> 35 * Each time a token is reduced a new state is created. 36 * <p> 37 * See Annex H on page 556 of the SGML handbook for more information. 38 * 39 * @see Parser 40 * @see DTD 41 * @see Element 42 * @see ContentModel 43 * @author Arthur van Hoff 44 */ 45 class ContentModelState { 46 ContentModel model; 47 long value; 48 ContentModelState next; 49 50 /** 51 * Create a content model state for a content model. 52 */ 53 public ContentModelState(ContentModel model) { 54 this(model, null, 0); 55 } 56 57 /** 58 * Create a content model state for a content model given the 59 * remaining state that needs to be reduce. 60 */ 61 ContentModelState(Object content, ContentModelState next) { 62 this(content, next, 0); 63 } 64 65 /** 66 * Create a content model state for a content model given the 67 * remaining state that needs to be reduce. 68 */ 69 ContentModelState(Object content, ContentModelState next, long value) { 70 this.model = (ContentModel)content; 71 this.next = next; 72 this.value = value; 73 } 74 75 /** 76 * Return the content model that is relevant to the current state. 77 */ 78 public ContentModel getModel() { 79 ContentModel m = model; 80 for (int i = 0; i < value; i++) { 81 if (m.next != null) { 82 m = m.next; 83 } else { 84 return null; 85 } 86 } 87 return m; 88 } 89 90 /** 91 * Check if the state can be terminated. That is there are no more 92 * tokens required in the input stream. 93 * @return true if the model can terminate without further input 94 */ 95 @SuppressWarnings("fallthrough") 96 public boolean terminate() { 97 switch (model.type) { 98 case '+': 99 if ((value == 0) && !(model).empty()) { 100 return false; 101 } 102 // Fall through okay? 103 case '*': 104 case '?': 105 return (next == null) || next.terminate(); 106 107 case '|': 108 for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) { 109 if (m.empty()) { 110 return (next == null) || next.terminate(); 111 } 112 } 113 return false; 114 115 case '&': { 116 ContentModel m = (ContentModel)model.content; 117 118 for (int i = 0 ; m != null ; i++, m = m.next) { 119 if ((value & (1L << i)) == 0) { 120 if (!m.empty()) { 121 return false; 122 } 123 } 124 } 125 return (next == null) || next.terminate(); 126 } 127 128 case ',': { 129 ContentModel m = (ContentModel)model.content; 130 for (int i = 0 ; i < value ; i++, m = m.next); 131 132 for (; (m != null) && m.empty() ; m = m.next); 133 if (m != null) { 134 return false; 135 } 136 return (next == null) || next.terminate(); 137 } 138 139 default: 140 return false; 141 } 142 } 143 144 /** 145 * Check if the state can be terminated. That is there are no more 146 * tokens required in the input stream. 147 * @return the only possible element that can occur next 148 */ 149 public Element first() { 150 switch (model.type) { 151 case '*': 152 case '?': 153 case '|': 154 case '&': 155 return null; 156 157 case '+': 158 return model.first(); 159 160 case ',': { 161 ContentModel m = (ContentModel)model.content; 162 for (int i = 0 ; i < value ; i++, m = m.next); 163 return m.first(); 164 } 165 166 default: 167 return model.first(); 168 } 169 } 170 171 /** 172 * Advance this state to a new state. An exception is thrown if the 173 * token is illegal at this point in the content model. 174 * @return next state after reducing a token 175 */ 176 public ContentModelState advance(Object token) { 177 switch (model.type) { 178 case '+': 179 if (model.first(token)) { 180 return new ContentModelState(model.content, 181 new ContentModelState(model, next, value + 1)).advance(token); 182 } 183 if (value != 0) { 184 if (next != null) { 185 return next.advance(token); 186 } else { 187 return null; 188 } 189 } 190 break; 191 192 case '*': 193 if (model.first(token)) { 194 return new ContentModelState(model.content, this).advance(token); 195 } 196 if (next != null) { 197 return next.advance(token); 198 } else { 199 return null; 200 } 201 202 case '?': 203 if (model.first(token)) { 204 return new ContentModelState(model.content, next).advance(token); 205 } 206 if (next != null) { 207 return next.advance(token); 208 } else { 209 return null; 210 } 211 212 case '|': 213 for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) { 214 if (m.first(token)) { 215 return new ContentModelState(m, next).advance(token); 216 } 217 } 218 break; 219 220 case ',': { 221 ContentModel m = (ContentModel)model.content; 222 for (int i = 0 ; i < value ; i++, m = m.next); 223 224 if (m.first(token) || m.empty()) { 225 if (m.next == null) { 226 return new ContentModelState(m, next).advance(token); 227 } else { 228 return new ContentModelState(m, 229 new ContentModelState(model, next, value + 1)).advance(token); 230 } 231 } 232 break; 233 } 234 235 case '&': { 236 ContentModel m = (ContentModel)model.content; 237 boolean complete = true; 238 239 for (int i = 0 ; m != null ; i++, m = m.next) { 240 if ((value & (1L << i)) == 0) { 241 if (m.first(token)) { 242 return new ContentModelState(m, 243 new ContentModelState(model, next, value | (1L << i))).advance(token); 244 } 245 if (!m.empty()) { 246 complete = false; 247 } 248 } 249 } 250 if (complete) { 251 if (next != null) { 252 return next.advance(token); 253 } else { 254 return null; 255 } 256 } 257 break; 258 } 259 260 default: 261 if (model.content == token) { 262 if (next == null && (token instanceof Element) && 263 ((Element)token).content != null) { 264 return new ContentModelState(((Element)token).content); 265 } 266 return next; 267 } 268 // PENDING: Currently we don't correctly deal with optional start 269 // tags. This can most notably be seen with the 4.01 spec where 270 // TBODY's start and end tags are optional. 271 // Uncommenting this and the PENDING in ContentModel will 272 // correctly skip the omit tags, but the delegate is not notified. 273 // Some additional API needs to be added to track skipped tags, 274 // and this can then be added back. 275 /* 276 if ((model.content instanceof Element)) { 277 Element e = (Element)model.content; 278 279 if (e.omitStart() && e.content != null) { 280 return new ContentModelState(e.content, next).advance( 281 token); 282 } 283 } 284 */ 285 } 286 287 // We used to throw this exception at this point. However, it 288 // was determined that throwing this exception was more expensive 289 // than returning null, and we could not justify to ourselves why 290 // it was necessary to throw an exception, rather than simply 291 // returning null. I'm leaving it in a commented out state so 292 // that it can be easily restored if the situation ever arises. 293 // 294 // throw new IllegalArgumentException("invalid token: " + token); 295 return null; 296 } 297 }