001: // Copyright 2007 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.tapestry.integration.app1.services;
016:
017: import static java.lang.String.format;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newStack;
020:
021: import java.io.BufferedInputStream;
022: import java.io.InputStream;
023: import java.net.URL;
024: import java.util.List;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.tapestry.integration.app1.data.Track;
028: import org.apache.tapestry.ioc.util.Stack;
029: import org.xml.sax.Attributes;
030: import org.xml.sax.InputSource;
031: import org.xml.sax.SAXException;
032: import org.xml.sax.XMLReader;
033: import org.xml.sax.helpers.DefaultHandler;
034: import org.xml.sax.helpers.XMLReaderFactory;
035:
036: /**
037: * Reads an iTunes music library file into a list of
038: * {@link org.apache.tapestry.integration.app1.data.Track} elements.
039: */
040: public class MusicLibraryParser {
041: private final Log _log;
042:
043: private static final int STATE_START = 0;
044:
045: private static final int STATE_PLIST = 1;
046:
047: private static final int STATE_DICT1 = 2;
048:
049: private static final int STATE_IGNORE = 3;
050:
051: private static final int STATE_DICT2 = 4;
052:
053: private static final int STATE_DICT_TRACK = 5;
054:
055: private static final int STATE_COLLECT_KEY = 6;
056:
057: private static final int STATE_COLLECT_VALUE = 7;
058:
059: private static class Item {
060: StringBuilder _buffer;
061:
062: boolean _ignoreCharacterData;
063:
064: int _priorState;
065:
066: void addContent(char buffer[], int start, int length) {
067: if (_ignoreCharacterData)
068: return;
069:
070: if (_buffer == null)
071: _buffer = new StringBuilder(length);
072:
073: _buffer.append(buffer, start, length);
074: }
075:
076: String getContent() {
077: if (_buffer != null)
078: return _buffer.toString().trim();
079: else
080: return null;
081: }
082:
083: Item(int priorState, boolean ignoreCharacterData) {
084: _priorState = priorState;
085: _ignoreCharacterData = ignoreCharacterData;
086: }
087: }
088:
089: private class Handler extends DefaultHandler {
090: private final List<Track> _tracks = newList();
091:
092: private Stack<Item> _stack = newStack();
093:
094: private int _state = STATE_START;
095:
096: /** Most recently seen key. */
097: private String _key;
098:
099: /** Currently building Track. */
100: private Track _track;
101:
102: public List<Track> getTracks() {
103: return _tracks;
104: }
105:
106: private Item peek() {
107: return _stack.peek();
108: }
109:
110: private void pop() {
111: _state = _stack.pop()._priorState;
112: }
113:
114: private void push(int newState) {
115: push(newState, true);
116: }
117:
118: protected void push(int newState, boolean ignoreCharacterData) {
119: Item item = new Item(_state, ignoreCharacterData);
120:
121: _stack.push(item);
122:
123: _state = newState;
124: }
125:
126: @Override
127: public void endElement(String uri, String localName,
128: String qName) throws SAXException {
129: end(getElementName(localName, qName));
130: }
131:
132: @Override
133: public void startElement(String uri, String localName,
134: String qName, Attributes attributes)
135: throws SAXException {
136: String elementName = getElementName(localName, qName);
137: begin(elementName);
138: }
139:
140: private String getElementName(String localName, String qName) {
141: return qName == null ? localName : qName;
142: }
143:
144: @Override
145: public void characters(char ch[], int start, int length)
146: throws SAXException {
147: peek().addContent(ch, start, length);
148: }
149:
150: private void begin(String element) {
151: switch (_state) {
152: case STATE_START:
153: enterStart(element);
154: return;
155:
156: case STATE_PLIST:
157: enterPlist(element);
158: return;
159:
160: case STATE_DICT1:
161: enterDict1(element);
162: return;
163:
164: case STATE_DICT2:
165: enterDict2(element);
166: return;
167:
168: case STATE_IGNORE:
169: push(STATE_IGNORE);
170: return;
171:
172: case STATE_DICT_TRACK:
173: enterDictTrack(element);
174: return;
175: }
176: }
177:
178: private void enterStart(String element) {
179: if (element.equals("plist")) {
180: push(STATE_PLIST);
181: return;
182: }
183: }
184:
185: private void enterPlist(String element) {
186: if (element.equals("dict")) {
187: push(STATE_DICT1);
188: return;
189: }
190:
191: push(STATE_IGNORE);
192: }
193:
194: private void enterDict1(String element) {
195: if (element.equals("dict")) {
196: push(STATE_DICT2);
197: return;
198: }
199:
200: push(STATE_IGNORE);
201: }
202:
203: private void enterDict2(String element) {
204: if (element.equals("dict")) {
205: beginDictTrack(element);
206: return;
207: }
208:
209: push(STATE_IGNORE);
210: }
211:
212: private void beginDictTrack(String element) {
213: _track = new Track();
214:
215: _tracks.add(_track);
216:
217: push(STATE_DICT_TRACK);
218: }
219:
220: private void enterDictTrack(String element) {
221: if (element.equals("key")) {
222: beginCollectKey(element);
223: return;
224: }
225:
226: beginCollectValue(element);
227: }
228:
229: private void beginCollectKey(String element) {
230: push(STATE_COLLECT_KEY, false);
231: }
232:
233: private void beginCollectValue(String element) {
234: push(STATE_COLLECT_VALUE, false);
235:
236: }
237:
238: private void end(String element) {
239: switch (_state) {
240: case STATE_COLLECT_KEY:
241:
242: endCollectKey(element);
243: return;
244:
245: case STATE_COLLECT_VALUE:
246: endCollectValue(element);
247: return;
248:
249: default:
250: pop();
251: }
252: }
253:
254: private void endCollectKey(String element) {
255: _key = peek().getContent();
256:
257: pop();
258: }
259:
260: private void endCollectValue(String element) {
261: String value = peek().getContent();
262:
263: pop();
264:
265: if (_key.equals("Name")) {
266: _track.setTitle(value);
267: return;
268: }
269:
270: if (_key.equals("Artist")) {
271: _track.setArtist(value);
272: return;
273: }
274:
275: if (_key.equals("Album")) {
276: _track.setAlbum(value);
277: return;
278: }
279:
280: if (_key.equals("Genre")) {
281: _track.setGenre(value);
282: return;
283: }
284:
285: if (_key.equals("Play Count")) {
286: _track.setPlayCount(Integer.parseInt(value));
287: return;
288: }
289:
290: if (_key.equals("Rating")) {
291: _track.setRating(Integer.parseInt(value));
292: return;
293: }
294:
295: // Many other keys are just ignored.
296: }
297:
298: @Override
299: public InputSource resolveEntity(String publicId,
300: String systemId) throws SAXException {
301: if (publicId.equals("-//Apple Computer//DTD PLIST 1.0//EN")) {
302: InputStream local = new BufferedInputStream(getClass()
303: .getResourceAsStream("PropertyList-1.0.dtd"));
304:
305: return new InputSource(local);
306: }
307:
308: // Perform normal processing, such as accessing via system id. That's
309: // what we want to avoid, since presentations are often given when there
310: // is no Internet connection.
311:
312: return null;
313: }
314: }
315:
316: public MusicLibraryParser(final Log log) {
317: _log = log;
318: }
319:
320: public List<Track> parseTracks(URL resource) {
321: _log.info(format("Parsing music library %s", resource));
322:
323: long start = System.currentTimeMillis();
324:
325: Handler handler = new Handler();
326:
327: try {
328: XMLReader reader = XMLReaderFactory.createXMLReader();
329:
330: reader.setContentHandler(handler);
331: reader.setEntityResolver(handler);
332:
333: InputSource source = new InputSource(resource.openStream());
334:
335: reader.parse(source);
336: } catch (Exception ex) {
337: throw new RuntimeException(ex);
338: }
339:
340: List<Track> result = handler.getTracks();
341: long elapsed = System.currentTimeMillis() - start;
342:
343: _log.info(format("Parsed %d tracks in %d ms", result.size(),
344: elapsed));
345:
346: return result;
347: }
348: }
|