Java tutorial
/*- * Copyright (C) 2009 Peter Baldwin * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.peterbaldwin.client.android.delicious; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.util.Log; // TODO: improve error messages public class DeliciousApiRequest implements Runnable { @SuppressWarnings("serial") private static class ConnectionException extends IOException { private final int mStatusCode; private ConnectionException(int statusCode) { mStatusCode = statusCode; } public int getStatusCode() { return mStatusCode; } } private static final String LOG_TAG = "DeliciousApiRequest"; // TODO: make error codes into constructor parameters? public static final int HANDLE_AUTH_ERROR = -2; public static final int HANDLE_ERROR = -1; public static final int HANDLE_DONE = 0; public static final int REQUEST_POSTS_ADD = 1; public static final int REQUEST_POSTS_SUGGEST = 2; public static final int REQUEST_TAGS_GET = 3; private static final int BUFFER_SIZE = 1024; private final int mRequestType; private final int mRequestId; private final Uri mUri; private final HttpClient mClient; private final DeliciousApiResponseHandler mResponseHandler; private final Handler mHandler; private File mCacheFile; private boolean mUseCache; private boolean mUpdateCache; private boolean mAlwaysUpdateCache; DeliciousApiRequest(int requestType, int requestId, Uri uri, HttpClient client, DeliciousApiResponseHandler responseHandler, Handler handler) { super(); mRequestType = requestType; mRequestId = requestId; mUri = uri; mClient = client; mResponseHandler = responseHandler; mHandler = handler; } private void setError(Message msg, Throwable t) { if (mCacheFile != null) { mCacheFile.delete(); } String error = String.valueOf(t); msg.what = HANDLE_ERROR; msg.obj = error; Log.e(LOG_TAG, error, t); } private InputStream readFromCache() throws IOException { long start = now(); try { if (mCacheFile == null) { throw new NullPointerException(); } if (mCacheFile.exists()) { InputStream in = new FileInputStream(mCacheFile); in = new BufferedInputStream(in); return in; } return null; } finally { logTiming("read cache", start); } } private InputStream updateCache(InputStream in) throws IOException { long start = now(); try { try { if (mCacheFile == null) { throw new NullPointerException(); } File parent = mCacheFile.getParentFile(); // An exception will be thrown below when trying to write // the file if the parent directories were not created. parent.mkdirs(); mCacheFile.createNewFile(); OutputStream fout = new FileOutputStream(mCacheFile); fout = new BufferedOutputStream(fout); ByteArrayOutputStream bout = new ByteArrayOutputStream(); try { byte[] buffer = new byte[BUFFER_SIZE]; while (true) { int size = in.read(buffer); if (size < 0) { break; } bout.write(buffer, 0, size); fout.write(buffer, 0, size); } } finally { bout.close(); fout.close(); } byte[] data = bout.toByteArray(); return new ByteArrayInputStream(data); } finally { in.close(); } } finally { logTiming("update cache", start); } } private InputStream readFromNetwork() throws IOException { long start = now(); try { HttpGet request = new HttpGet(String.valueOf(mUri)); HttpResponse response = mClient.execute(request); StatusLine status = response.getStatusLine(); int statusCode = status.getStatusCode(); if (statusCode != HttpStatus.SC_OK) { throw new ConnectionException(statusCode); } HttpEntity entity = response.getEntity(); InputStream in = entity.getContent(); in = new BufferedInputStream(in); return in; } finally { logTiming("read network", start); } } private static long now() { return SystemClock.uptimeMillis(); } private static void logTiming(String action, long start) { long end = now(); long duration = end - start; String msg = String.format("%s took %d ms", action, duration); Log.d(LOG_TAG, msg); } /** * {@inheritDoc} */ public void run() { Message msg = mHandler.obtainMessage(); msg.arg1 = mRequestType; msg.arg2 = mRequestId; msg.what = HANDLE_ERROR; msg.obj = "unknown error"; boolean cacheUpdated = false; try { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); ContentHandler handler = mResponseHandler.getContentHandler(); reader.setContentHandler(handler); InputStream in = null; if (in == null && mUseCache && mCacheFile != null) { in = readFromCache(); } if (in == null) { in = readFromNetwork(); if (mUpdateCache && mCacheFile != null) { in = updateCache(in); cacheUpdated = true; } } try { InputSource input = new InputSource(in); long start = now(); try { reader.parse(input); } finally { logTiming("parse", start); } } finally { in.close(); } msg.what = HANDLE_DONE; msg.obj = mResponseHandler; } catch (ConnectionException e) { setError(msg, e); int statusCode = e.getStatusCode(); if (statusCode == HttpStatus.SC_UNAUTHORIZED) { msg.what = HANDLE_AUTH_ERROR; msg.obj = "invalid username or password"; } else { msg.what = HANDLE_ERROR; msg.obj = "unexpected response: " + statusCode; } } catch (IOException e) { setError(msg, e); } catch (ParserConfigurationException e) { setError(msg, e); } catch (SAXException e) { setError(msg, e); } catch (RuntimeException e) { setError(msg, e); } catch (Error e) { setError(msg, e); } finally { mHandler.sendMessage(msg); } if (msg.what != HANDLE_AUTH_ERROR && !cacheUpdated && mAlwaysUpdateCache && mCacheFile != null) { // If the authentication is valid, but the cache was not updated, // silently update the cache in the background after dispatching the // result to the handler. try { InputStream in = readFromNetwork(); in = updateCache(in); in.close(); Log.i(LOG_TAG, "cache file updated: " + mCacheFile); } catch (IOException e) { Log.e(LOG_TAG, "error updating cache", e); } catch (RuntimeException e) { Log.e(LOG_TAG, "error updating cache", e); } catch (Error e) { Log.e(LOG_TAG, "error updating cache", e); } } } public void setCacheFile(File cacheFile) { mCacheFile = cacheFile; } public void setUseCache(boolean useCache) { mUseCache = useCache; } public void setUpdateCache(boolean updateCache) { mUpdateCache = updateCache; } public void setAlwaysUpdateCache(boolean alwaysUpdateCache) { mAlwaysUpdateCache = alwaysUpdateCache; } }