001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.hivemind.parse;
016
017 import java.util.ArrayList;
018 import java.util.HashMap;
019 import java.util.List;
020 import java.util.Map;
021
022 import org.apache.hivemind.ApplicationRuntimeException;
023 import org.apache.hivemind.HiveMind;
024 import org.apache.hivemind.Location;
025 import org.apache.hivemind.Resource;
026 import org.apache.hivemind.impl.LocationImpl;
027 import org.xml.sax.Attributes;
028 import org.xml.sax.Locator;
029 import org.xml.sax.SAXException;
030 import org.xml.sax.SAXParseException;
031 import org.xml.sax.helpers.DefaultHandler;
032
033 /**
034 * Abstract super-class for parsers based around the SAX event model. This class provides support
035 * for managing a stack of elements, making it reasonable to establish relationships between
036 * elements. It also assists in setting the {@link org.apache.hivemind.Location} of elements as they
037 * are created.
038 * <p>
039 * This support is structured around both XML but is suited towards configuration files rather than
040 * documents, in that the <em>content</em> (parsable character data) within an element is
041 * concatinated together and tracked as a single blob.
042 *
043 * @author Howard Lewis Ship
044 */
045 public abstract class AbstractParser extends DefaultHandler
046 {
047
048 /**
049 * The parser is built around a stack of these Items. This used to figure out the current state,
050 * the element being processed, and the matching descriptor object.
051 */
052 private static class Item
053 {
054 StringBuffer _buffer;
055
056 String _elementName;
057
058 boolean _ignoreCharacterData;
059
060 Object _object;
061
062 /**
063 * Prior state of the parser before this item was pushed.
064 */
065 int _priorState;
066
067 Item(String elementName, Object object, int priorState, boolean ignoreCharacterData)
068 {
069 _elementName = elementName;
070 _object = object;
071 _priorState = priorState;
072 _ignoreCharacterData = ignoreCharacterData;
073 }
074
075 void addContent(char[] buffer, int start, int length)
076 {
077 if (_ignoreCharacterData)
078 return;
079
080 if (_buffer == null)
081 _buffer = new StringBuffer(length);
082
083 _buffer.append(buffer, start, length);
084 }
085
086 String getContent()
087 {
088 if (_buffer != null)
089 return _buffer.toString().trim();
090
091 return null;
092 }
093 }
094
095 private int _currentColumn;
096
097 private int _currentLine;
098
099 private Location _location;
100
101 private Locator _locator;
102
103 private Resource _resource;
104
105 private List _stack;
106
107 private int _state;
108
109 private Item _top;
110
111 /**
112 * Accepts parseable character data from within an element and applies it to the top stack
113 * element. This may be invoked multiple times by the parser, and the overall data will
114 * accumulate. This content can be retrieved via {@link #peekContent()}.
115 */
116 public void characters(char[] ch, int start, int length) throws SAXException
117 {
118 _top.addContent(ch, start, length);
119 }
120
121 /**
122 * Invokes {@link #fatalError(SAXParseException)}.
123 */
124 public void error(SAXParseException ex) throws SAXException
125 {
126 fatalError(ex);
127 }
128
129 /**
130 * @param ex
131 * exception to be thrown
132 * @throws SAXParseException
133 */
134 public void fatalError(SAXParseException ex) throws SAXException
135 {
136 throw ex;
137 }
138
139 /**
140 * Returns a "path" to the current element, as a series of element names seperated by slashes,
141 * i.e., "top/middle/leaf".
142 */
143 protected String getElementPath()
144 {
145 StringBuffer buffer = new StringBuffer();
146
147 int count = _stack.size();
148 for (int i = 0; i < count; i++)
149 {
150 if (i > 0)
151 buffer.append('/');
152
153 Item item = (Item) _stack.get(i);
154
155 buffer.append(item._elementName);
156 }
157
158 return buffer.toString();
159 }
160
161 /**
162 * Returns the current lcoation, as reported by the parser.
163 */
164 protected Location getLocation()
165 {
166 int line = _locator.getLineNumber();
167 int column = _locator.getColumnNumber();
168
169 if (line != _currentLine || column != _currentColumn)
170 _location = null;
171
172 if (_location == null)
173 _location = new LocationImpl(_resource, line, column);
174
175 _currentLine = line;
176 _currentColumn = column;
177
178 return _location;
179 }
180
181 /**
182 * Returns the {@link Resource} being parsed (as set by {@link #initializeParser(Resource, int)}.
183 */
184
185 protected Resource getResource()
186 {
187 return _resource;
188 }
189
190 /**
191 * Returns the current state of the parser. State is initially set by
192 * {@link #initializeParser(Resource, int)} and is later updated by
193 * {@link #push(String, Object, int, boolean)} and {@link #pop()}.
194 */
195 protected int getState()
196 {
197 return _state;
198 }
199
200 /**
201 * Initializes the parser; this should be called before any SAX parse events are received.
202 *
203 * @param resource
204 * the resource being parsed (used for some error messages)
205 * @param startState
206 * the initial state of the parser (the interpretation of state is determined by
207 * subclasses)
208 */
209 protected void initializeParser(Resource resource, int startState)
210 {
211 _resource = resource;
212 _stack = new ArrayList();
213
214 _location = null;
215 _state = startState;
216 }
217
218 /**
219 * Peeks at the top element on the stack, and returns its content (the accumuulated parseable
220 * character data directly enclosed by its start/end tags.
221 */
222 protected String peekContent()
223 {
224 return _top.getContent();
225 }
226
227 /**
228 * Peeks at the top element on the stack and returns its element name.
229 */
230 protected String peekElementName()
231 {
232 return _top._elementName;
233 }
234
235 /**
236 * Peeks at the top element on the stack and returns the object for that element.
237 */
238
239 protected Object peekObject()
240 {
241 return _top._object;
242 }
243
244 /**
245 * Invoked when the closing tag for an element is enountered {i.e, from
246 * {@link #endElement(String, String, String)}. This removes the corresponding item from the
247 * stack, and sets the parser state back to the (new) top element's state.
248 */
249 protected void pop()
250 {
251 int count = _stack.size();
252
253 _state = _top._priorState;
254
255 _stack.remove(count - 1);
256
257 if (count == 1)
258 _top = null;
259 else
260 _top = (Item) _stack.get(count - 2);
261 }
262
263 /**
264 * Enters a new state, pushing an object onto the stack. Invokes
265 * {@link #push(String, Object, int, boolean)}, and ignores character data within the element.
266 *
267 * @param elementName
268 * the element whose start tag was just parsed
269 * @param object
270 * the object created to represent the new object
271 * @param state
272 * the new state for the parse
273 */
274 protected void push(String elementName, Object object, int state)
275 {
276 push(elementName, object, state, true);
277 }
278
279 /**
280 * Enters a new state, pusubhing an object onto the stack. If the object implements
281 * {@link org.apache.hivemind.LocationHolder} then its location property is set to the
282 * current location.
283 *
284 * @param elementName
285 * the element whose start tag was just parsed
286 * @param object
287 * the object created to represent the new object
288 * @param state
289 * the new state for the parse
290 * @param ignoreCharacterData
291 * if true, then any character data (typically whitespace) directly enclosed by the
292 * element is ignored
293 */
294 protected void push(String elementName, Object object, int state, boolean ignoreCharacterData)
295 {
296 HiveMind.setLocation(object, getLocation());
297
298 Item item = new Item(elementName, object, _state, ignoreCharacterData);
299
300 _stack.add(item);
301
302 _top = item;
303 _state = state;
304 }
305
306 /**
307 * Resets all state after a parse.
308 */
309 protected void resetParser()
310 {
311 _resource = null;
312 _locator = null;
313 _stack = null;
314 _location = null;
315 }
316
317 /**
318 * Invoked by the parser, the locator is stored and later used by {@link #getLocation()}.
319 */
320 public void setDocumentLocator(Locator locator)
321 {
322 _locator = locator;
323 }
324
325 /**
326 * Forces a change to a specific state.
327 */
328 protected void setState(int state)
329 {
330 _state = state;
331 }
332
333 /**
334 * Invoked when an unexpected element is parsed (useful for parses that don't perform
335 * validation, or when there's no DTD).
336 *
337 * @throws ApplicationRuntimeException
338 * describing the situation
339 */
340 protected void unexpectedElement(String elementName)
341 {
342 throw new ApplicationRuntimeException(ParseMessages.unexpectedElement(
343 elementName,
344 getElementPath()), getLocation(), null);
345 }
346
347 /**
348 * Ocassionaly it is necessary to "change our mind" about what's on the top of the stack.
349 *
350 * @param object
351 * the new object for the top stack element
352 */
353 protected void updateObject(Object object)
354 {
355 _top._object = object;
356 }
357
358 /**
359 * Invokes {@link #fatalError(SAXParseException)}.
360 */
361 public void warning(SAXParseException ex) throws SAXException
362 {
363 fatalError(ex);
364 }
365
366 private Map constructAttributesMap(Attributes attributes)
367 {
368 Map result = new HashMap();
369 int count = attributes.getLength();
370
371 for (int i = 0; i < count; i++)
372 {
373 String key = attributes.getLocalName(i);
374
375 if (HiveMind.isBlank(key))
376 key = attributes.getQName(i);
377
378 String value = attributes.getValue(i);
379
380 result.put(key, value);
381 }
382
383 return result;
384 }
385
386 /**
387 * Invoked when an element's start tag is recognized. The element and attributes are provided to
388 * the subclass for further processing.
389 */
390 protected abstract void begin(String elementName, Map attributes);
391
392 /**
393 * Invoked when an element's close tag is recognized. The element is provided. The content of
394 * the element (the unparsed whitespace within the element's tags) is available via
395 * {@link #peekContent()}.
396 */
397
398 protected abstract void end(String elementName);
399
400 public void endElement(String uri, String localName, String qName) throws SAXException
401 {
402 end(getElementName(localName, qName));
403 }
404
405 public void startElement(String uri, String localName, String qName, Attributes attributes)
406 throws SAXException
407 {
408 String elementName = getElementName(localName, qName);
409
410 begin(elementName, constructAttributesMap(attributes));
411 }
412
413 private String getElementName(String localName, String qName)
414 {
415 return qName != null ? qName : localName;
416 }
417 }