diff --git a/src/de/schildbach/pte/AbstractHafasProvider.java b/src/de/schildbach/pte/AbstractHafasProvider.java index 46560667..5506a6b8 100644 --- a/src/de/schildbach/pte/AbstractHafasProvider.java +++ b/src/de/schildbach/pte/AbstractHafasProvider.java @@ -19,6 +19,7 @@ package de.schildbach.pte; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -53,6 +54,7 @@ import de.schildbach.pte.dto.StationDepartures; import de.schildbach.pte.dto.Stop; import de.schildbach.pte.util.Color; import de.schildbach.pte.util.ParserUtils; +import de.schildbach.pte.util.StringReplaceReader; import de.schildbach.pte.util.XmlPullUtil; /** @@ -426,17 +428,20 @@ public abstract class AbstractHafasProvider implements NetworkProvider protected QueryDeparturesResult xmlQueryDepartures(final String uri, final int stationId) throws IOException { - // System.out.println(uri); - - InputStream is = null; + StringReplaceReader reader = null; try { - is = ParserUtils.scrapeInputStream(uri); + reader = new StringReplaceReader(new InputStreamReader(ParserUtils.scrapeInputStream(uri), DEFAULT_ENCODING), "Ringbahn ->", + "Ringbahn ->"); + reader.replace("Ringbahn <-", "Ringbahn <-"); + + // System.out.println(uri); + // ParserUtils.printFromReader(reader); final XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); final XmlPullParser pp = factory.newPullParser(); - pp.setInput(is, DEFAULT_ENCODING); + pp.setInput(reader); pp.nextTag(); @@ -589,8 +594,8 @@ public abstract class AbstractHafasProvider implements NetworkProvider } finally { - if (is != null) - is.close(); + if (reader != null) + reader.close(); } } diff --git a/src/de/schildbach/pte/util/CharQueue.java b/src/de/schildbach/pte/util/CharQueue.java new file mode 100644 index 00000000..1d0698c3 --- /dev/null +++ b/src/de/schildbach/pte/util/CharQueue.java @@ -0,0 +1,299 @@ +package de.schildbach.pte.util; + +/* + * Copyright (C) 1997 Roger Whitney + * + * This file is part of the San Diego State University Java Library. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * This class implements a characater queue. Yes the JKD does contain a general queue. However that queue operates on + * objects. This queue just handles char elements. Use in IO operations where converting chars to objects will be too + * expensive. + * + * @version 1.0 21 August 1997 + * @author Roger Whitney (whitney@cs.sdsu.edu) + */ + +final public class CharQueue +{ + /* + * Class invariant, queueRear is the location the next queue item should be placed If the queue is not empty, + * queueFront is the location of the first item in the queue + */ + + private char[] queueElements; + private int queueFront; + private int queueRear; + private int elementCount; // number of elements in the queue + + public static final int DEFAULT_QUEUE_SIZE = 256; + + public CharQueue(int Size) + { + queueElements = new char[Size]; + queueFront = 0; + queueRear = 0; + elementCount = 0; + } + + public CharQueue() + { + this(DEFAULT_QUEUE_SIZE); + } + + /** + * Returns the current number of locations for chars in queue + */ + public int capacity() + { + return queueElements.length; + } + + /** + * Returns true if the queue is empty + */ + public boolean isEmpty() + { + if (elementCount == 0) + return true; + else + return false; + } + + /** + * Returns true if the queue is full + */ + public boolean isFull() + { + if (elementCount >= capacity()) + return true; + else + return false; + } + + /** + * Returns the number of chars in the queue + */ + public int size() + { + return elementCount; + } + + /** + * Returns string representation of the queue + */ + @Override + public String toString() + { + StringBuffer queueString = new StringBuffer(elementCount); + if (queueFront < queueRear) + { + queueString.append(queueElements, queueFront, elementCount); + } + else + { + int elementsFromFrontToEnd = capacity() - queueFront; + queueString.append(queueElements, queueFront, elementsFromFrontToEnd); + queueString.append(queueElements, 0, queueRear); + } + + return queueString.toString(); + } + + /** + * Returns the current number of unused locations in the queue + */ + public int unusedCapacity() + { + return capacity() - size(); + } + + /** + * Removes front char from the queue and returns the char + */ + public char dequeue() + { + char itemRemoved = queueElements[queueFront]; + queueFront = (queueFront + 1) % capacity(); + elementCount--; + return itemRemoved; + } + + /** + * Fills charsRemoved with chars removed from the queue. If charsRemoved is larger than queue then charsRemoved is + * not completely filled + * + * @return actual number of chars put in charsRemoved + */ + public int dequeue(char[] charsRemoved) + { + return dequeue(charsRemoved, 0, charsRemoved.length); + } + + /** + * Places chars from queue in charsRemoved starting at charsRemoved[offset]. Will place numCharsRequested into + * charsRemoved if queue has enougth chars. + * + * @return actual number of chars put in charsRemoved + */ + public int dequeue(char[] charsRemoved, int offset, int numCharsRequested) + { + // Don't return more chars than are in the queue + int numCharsToReturn = Math.min(numCharsRequested, elementCount); + + int numCharsAtEnd = capacity() - queueFront; + + // Are there enough characters after front pointer? + if (numCharsAtEnd >= numCharsToReturn) + { + // arraycopy is about 20 times faster than coping element by element + System.arraycopy(queueElements, queueFront, charsRemoved, offset, numCharsToReturn); + } + else + { + // Handle wrap around + System.arraycopy(queueElements, queueFront, charsRemoved, offset, numCharsAtEnd); + System.arraycopy(queueElements, 0, charsRemoved, offset + numCharsAtEnd, numCharsToReturn - numCharsAtEnd); + } + + queueFront = (queueFront + numCharsToReturn) % capacity(); + elementCount = elementCount - numCharsToReturn; + return numCharsToReturn; + } + + /** + * Returns an array containing all chars in the queue. Afterwards queue is empty. + */ + public char[] dequeueAll() + { + char[] contents = new char[elementCount]; + dequeue(contents); + return contents; + } + + /** + * Returns the front char from the queue without removing it + */ + public char peek() + { + return queueElements[queueFront]; + } + + /** + * Adds charToAdd to the end of the queue + */ + public void enqueue(char charToAdd) + { + if (isFull()) + grow(); + + queueElements[queueRear] = charToAdd; + queueRear = (queueRear + 1) % capacity(); + elementCount++; + } + + /** + * Adds charsToAdd to the end of the queue + */ + public void enqueue(String charsToAdd) + { + enqueue(charsToAdd.toCharArray()); + } + + /** + * Adds all elements of charsToAdd to the end of the queue + */ + public void enqueue(char[] charsToAdd) + { + enqueue(charsToAdd, 0, charsToAdd.length); + } + + /** + * Adds numCharsToAdd elements of charsToAdd, starting with charsToAdd[offset] to the end of the queue + */ + public void enqueue(char[] charsToAdd, int offset, int numCharsToAdd) + { + if (numCharsToAdd > unusedCapacity()) + grow(Math.max(numCharsToAdd + 32, capacity() * 2)); + // 32 to insure some spare capacity after growing + + int numSpacesAtEnd = capacity() - queueRear; + + // Are there enough spaces after rear pointer? + if (numSpacesAtEnd >= numCharsToAdd) + { + System.arraycopy(charsToAdd, offset, queueElements, queueRear, numCharsToAdd); + } + else + // Handle wrap around + { + System.arraycopy(charsToAdd, offset, queueElements, queueRear, numSpacesAtEnd); + System.arraycopy(charsToAdd, offset + numSpacesAtEnd, queueElements, 0, numCharsToAdd - numSpacesAtEnd); + } + + queueRear = (queueRear + numCharsToAdd) % capacity(); + elementCount = elementCount + numCharsToAdd; + } + + /** + * Clears the queue so it has no more elements in it + */ + public void clear() + { + queueFront = 0; + queueRear = 0; + elementCount = 0; + } + + /** + * Grows the queue. Growth policy insures amortized cost per insert is O(1) + */ + private void grow() + { + // Doubling queue insures that amortized cost per insert is O(1) + if (capacity() <= 16) + grow(32); + else if (capacity() <= 1024) + grow(capacity() * 2); + else + grow((int) (capacity() * 1.5)); + } + + /** + * Grows the queue to the given new size + */ + private void grow(int newSize) + { + char[] newQueue = new char[newSize]; + + if (queueFront < queueRear) + { + System.arraycopy(queueElements, queueFront, newQueue, 0, elementCount); + } + else + { + int elementsFromFrontToEnd = capacity() - queueFront; + System.arraycopy(queueElements, queueFront, newQueue, 0, elementsFromFrontToEnd); + System.arraycopy(queueElements, 0, newQueue, elementsFromFrontToEnd, queueRear); + } + + queueElements = newQueue; + queueFront = 0; + queueRear = elementCount; + } +} diff --git a/src/de/schildbach/pte/util/ParserUtils.java b/src/de/schildbach/pte/util/ParserUtils.java index 3a74cb05..ca887673 100644 --- a/src/de/schildbach/pte/util/ParserUtils.java +++ b/src/de/schildbach/pte/util/ParserUtils.java @@ -386,6 +386,18 @@ public final class ParserUtils System.out.println(m.group(1)); } + public static void printFromReader(final Reader reader) throws IOException + { + while (true) + { + final int c = reader.read(); + if (c == -1) + return; + + System.out.print((char) c); + } + } + public static String urlEncode(final String str) { try diff --git a/src/de/schildbach/pte/util/StringReplaceReader.java b/src/de/schildbach/pte/util/StringReplaceReader.java new file mode 100644 index 00000000..2bec0d3f --- /dev/null +++ b/src/de/schildbach/pte/util/StringReplaceReader.java @@ -0,0 +1,367 @@ +package de.schildbach.pte.util; + +/* + * Copyright (C) 1997 Roger Whitney + * + * This file is part of the San Diego State University Java Library. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +import java.io.BufferedReader; +import java.io.FilterReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/** + * Given a string pattern, a string replacementPattern and an input stream, this class will replace all + * occurances of pattern with replacementPattern in the inputstream. You can give multiple + * pattern-replacementPattern pairs. Multiple pairs are done in order they are given. If first pair is "cat"-"dog" and + * second pair is "dog"-"house", then the result will be all occurences of "cat" or "dog" will be replaced with "house". + * + * @version 0.6 21 August 1997 + * @since version 0.5, Fixed error that occured when input was shorter than the pattern + * @author Roger Whitney (whitney@cs.sdsu.edu) + */ + +public class StringReplaceReader extends FilterReader implements Cloneable +{ + protected CharQueue outputBuffer; // holds filtered data + protected char[] inputBuffer; + protected int[] shiftTable; // quick search shift table + protected int inputBufferCharCount; // number of chars in inputBuffer + + protected char[] patternToFind = null; + protected char[] replacementPattern = null; + + protected boolean reachedEOF = false; + protected static int EOFIndicator = -1; + protected static int DEFAULT_BUFFER_SIZE = 1024; + + /** + * Create an StringReplaceReader object that will replace all occurrences ofpattern with replacementPattern in the + * Reader in. + */ + public StringReplaceReader(Reader in, String pattern, String replacementPattern) + { + super(in); + patternToFind = pattern.toCharArray(); + this.replacementPattern = replacementPattern.toCharArray(); + + allocateBuffers(); + } + + /** + * Create an StringReplaceReader object that will replace all occurrences of pattern with replacementPattern in the + * inputstream in. + */ + public StringReplaceReader(InputStream in, String pattern, String replacementPattern) + { + this(new BufferedReader(new InputStreamReader(in)), pattern, replacementPattern); + } + + /** + * Create an StringReplaceReader object that will replace all occurrences of pattern with replacementPattern in the + * string input. + */ + public StringReplaceReader(String input, String pattern, String replacementPattern) + { + this(new StringReader(input), pattern, replacementPattern); + } + + /** + * Returns the entire contents of the input stream. + */ + public String contents() throws IOException + { + StringBuffer contents = new StringBuffer(1024); + int readSize = 512; + + char[] filteredChars = new char[readSize]; + int charsRead = read(filteredChars, 0, readSize); + + while (charsRead != EOFIndicator) + { + contents.append(filteredChars, 0, charsRead); + charsRead = read(filteredChars, 0, readSize); + } + + return contents.toString(); + } + + /** + * Adds another pattern-replacementPattern pair. All occurrences of pattern will be replaced with + * replacementPattern. + * + * @exception OutOfMemoryError + * if there is not enough memory to add new pattern-replacementPattern pair + */ + public void replace(String pattern, String replacementPattern) throws OutOfMemoryError + { + // Chain StringReplaceReader objects. Clone current object + // add clone to input stream to insure it filters before this + // object, which gets the new pattern-replacement pair + if (patternToFind != null) + { + // Replace this with clone + try + { + StringReplaceReader currentReplace = (StringReplaceReader) this.clone(); + in = currentReplace; + } + catch (CloneNotSupportedException x) + { + } + } + patternToFind = pattern.toCharArray(); + this.replacementPattern = replacementPattern.toCharArray(); + allocateBuffers(); + + reachedEOF = false; + } + + /** + * Read characters into a portion of an array. This method will block until some input is available, an I/O error + * occurs, or the end of the stream is reached. + * + * @parm buffer Destination buffer + * @parm offset location in buffer to start storing characters + * @parm charsToRead maximum characters to read + * @return number of characters actually read, -1 if reah EOF on reading first character + * @exception IOException + * if an I/O error occurs + */ + @Override + public int read(char[] buffer, int offset, int charsToRead) throws IOException + { + int charsRead = 0; + + while ((charsRead < charsToRead) && (!eof())) + { + if (outputBuffer.isEmpty()) + { + fillInputWindow(); + filterInput(); + } + charsRead += outputBuffer.dequeue(buffer, offset + charsRead, charsToRead - charsRead); + } + if (charsRead > 0) + return charsRead; + else if (outputBuffer.size() > 0) + { + charsRead = outputBuffer.dequeue(buffer, offset, charsToRead); + return charsRead; + } + else if ((eof()) && (inputBufferCharCount > 0) && (inputBufferCharCount < patternToFind.length)) + { + // remaining input is less than length of pattern + transferRemainingInputToOutputBuffer(); + System.out.println(">> End << " + outputBuffer); + charsRead = outputBuffer.dequeue(buffer, offset, charsToRead); + return charsRead; + } + else if (eof()) + return EOFIndicator; + else + // this should never happen + throw new IOException("Read attempted. Did not reach EOF and " + " no chars were read"); + } + + /** + * Call when remaining input is less than the pattern size, so pattern can not exist in remaining input. Just shift + * all input to output. Assumes that have reached EOF and inputBufferCharCount < patternToFind.length + */ + private void transferRemainingInputToOutputBuffer() + { + outputBuffer.enqueue(inputBuffer, 0, inputBufferCharCount); + inputBufferCharCount = 0; + } + + /** + * Returns the next character in the inputstream with string replacement done. + * + * @exception IOException + * if error occurs reading io stream + */ + @Override + public int read() throws IOException + { + char[] output = new char[1]; + int charsRead = read(output, 0, 1); + if (charsRead == EOFIndicator) + return EOFIndicator; + else if (charsRead == 1) + return output[0]; + else + throw new IOException("Single Read attempted. Did not reach EOF and " + " no chars were read"); + + } + + /** + * Determines if a previous ASCII I/O operation caught End Of File. + * + * @return true if end of file was reached. + */ + public boolean eof() + { + return reachedEOF; + } + + /** + * Read inpout to see if we have found the pattern. Requires: When this is called we have already have read + * first character in pattern.
+ * Side Effects: After attempt to find pattern, output buffer contains either the replacement pattern or all + * characters we konw are not part of pattern. + */ + protected void filterInput() throws IOException + { + // Use quick-search to find pattern. Fill inputBuffer with text. + // Process all text in inputBuffer. Place processed text in + // outputBuffer. + + int searchStart = 0; + int windowStart = 0; + int patternLength = patternToFind.length; + + // Search until pattern extends past end of inputBuffer + while (searchStart < inputBufferCharCount - patternLength + 1) + { + boolean foundPattern = true; + + // The search + for (int index = 0; index < patternLength; index++) + if (patternToFind[index] != inputBuffer[index + searchStart]) + { + foundPattern = false; + break; // for loop + } + + if (foundPattern) + { + // move text before pattern + outputBuffer.enqueue(inputBuffer, windowStart, searchStart - windowStart); + + replacementPatternToBuffer(); + windowStart = searchStart + patternLength; + searchStart = windowStart; + } + else + { + // look farther along in inputBuffer + int charLocationAfterPattern = searchStart + patternLength; + + if (charLocationAfterPattern >= inputBufferCharCount) + searchStart += 1; + else + searchStart += getShift(inputBuffer[charLocationAfterPattern]); + } + } + + if (searchStart > inputBufferCharCount) + searchStart = inputBufferCharCount; + + // move chars already searched + if (reachedEOF) + { + outputBuffer.enqueue(inputBuffer, windowStart, inputBufferCharCount - windowStart); + inputBufferCharCount = 0; + } + else + { + outputBuffer.enqueue(inputBuffer, windowStart, searchStart - windowStart); + System.arraycopy(inputBuffer, searchStart, inputBuffer, 0, inputBufferCharCount - searchStart); + + inputBufferCharCount = inputBufferCharCount - searchStart; + } + } + + /** + * Fill sliding input window with chars from input Read until window is full or reach EOF + */ + final protected void fillInputWindow() throws IOException + { + int charsToRead = inputBuffer.length - inputBufferCharCount; + + int firstEmptySlotInWindow = inputBufferCharCount; + + int charsRead = in.read(inputBuffer, firstEmptySlotInWindow, charsToRead); + + if (charsRead == charsToRead) // full read + { + inputBufferCharCount = inputBufferCharCount + charsRead; + charsToRead = 0; + } + else if (charsRead > 0) // parial read + { + inputBufferCharCount = inputBufferCharCount + charsRead; + charsToRead = charsToRead - charsRead; + + } + else if (charsRead == EOFIndicator) + { + reachedEOF = true; + } + else + throw new IOException("Read attempted. Did not reach EOF and " + " no chars were read"); + + } + + /** + * Return the number of positions we can shift pattern when findMyShift is character in inputBuffer after the + * pattern + */ + protected int getShift(char findMyShift) + { + if (findMyShift >= shiftTable.length) + return 1; + else + return shiftTable[findMyShift]; + } + + /** + * Put replacement pattern in output buffer. Subclass overrides for more complex replacement + */ + protected void replacementPatternToBuffer() + { + outputBuffer.enqueue(replacementPattern); + } + + private void allocateBuffers() + { + outputBuffer = new CharQueue(DEFAULT_BUFFER_SIZE); + + inputBuffer = new char[Math.max(patternToFind.length + 1, DEFAULT_BUFFER_SIZE)]; + inputBufferCharCount = 0; + // allocate for most ascii characters + shiftTable = new int[126]; + + // build shiftTable for quick search + // Entry for character X contains how far to shift + // pattern when pattern does not match text and + // character X is the character in text after end of + // pattern + + // Default for characters not in pattern + for (int k = 0; k < shiftTable.length; k++) + shiftTable[k] = patternToFind.length + 1; + + for (int k = 0; k < patternToFind.length; k++) + if (patternToFind[k] < shiftTable.length) + shiftTable[patternToFind[k]] = patternToFind.length - k; + } +}