package org.xmlrpc.android; import java.io.IOException; import java.io.InputStream; /** * A LoggedInputStream adds logging functionality to another input stream. * <p>Note that calls on a LoggedInputStream are passed "as-is" to the underlying stream.</p> * * * <p>We're using a LoggedInputStream in {@code XMLRPClient.java} to log the XML-RPC response document in case of parser errors.<br /> * * There are plenty of other ways to log the response, but a {@code XmlPullParser} wants an InputStream as input parameter, and * a LoggedInputStream seems the most reliable solution, with the smallest memory footprint.<br /> * Below are other examples of logging we tried:</p> * <ul> * <li>Read the first 1000 characters from the original input stream, then create a new SequenceInputStream with both the characters just read (a new ByteArrayInputStream), * and the original input stream.</li> * <li>Read the whole content in a String and log it, then create an StringInputStream over the string, and pass the new stream to the parser.</li> * </ul> */ public final class LoggedInputStream extends InputStream { private final InputStream inputStream; private final static int MAX_LOG_SIZE = 1000; private final byte[] loggedString = new byte[MAX_LOG_SIZE]; private int loggedStringSize = 0; public LoggedInputStream(InputStream input) { this.inputStream = input; } @Override public int available() throws IOException { return inputStream.available(); } @Override public void close() throws IOException { inputStream.close(); } @Override public void mark(int readlimit) { inputStream.mark(readlimit); } @Override public boolean markSupported() { return inputStream.markSupported(); } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { int bytesRead = inputStream.read(buffer, byteOffset, byteCount); if (bytesRead != -1) { log(buffer, byteOffset, bytesRead); } return bytesRead; } @Override public int read(byte[] buffer) throws IOException { return this.read(buffer, 0, buffer.length); } @Override public int read() throws IOException { int characterRead = inputStream.read(); if (characterRead != -1) { log(characterRead); } return characterRead; } @Override public synchronized void reset() throws IOException { inputStream.reset(); } @Override public long skip(long byteCount) throws IOException { return inputStream.skip(byteCount); } private void log(byte[] inputArray, int byteOffset, int byteCount) { int availableSpace = MAX_LOG_SIZE - loggedStringSize; if (availableSpace <= 0) { return; } int bytesLength = Math.min(availableSpace, byteCount); int startingPosition = MAX_LOG_SIZE - availableSpace; System.arraycopy(inputArray, byteOffset, loggedString, startingPosition, bytesLength); loggedStringSize += bytesLength; } private void log(int inputChar) { byte[] logThis = {(byte) inputChar}; log(logThis, 0, 1); } public String getResponseDocument() { if (loggedStringSize == 0) { return ""; } else { return new String(loggedString, 0, loggedStringSize); } } }