Java tutorial
/* Dubsar Dictionary Project Copyright (C) 2010-15 Jimmy Dee 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * Package for Dubsar model code. */ package com.dubsar_dictionary.Dubsar.model; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.http.ConnectionClosedException; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.HttpClientParams; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.message.BasicHeader; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONTokener; import android.content.Context; import android.content.SharedPreferences; import android.net.http.AndroidHttpClient; import android.os.Build; import android.util.Log; import com.dubsar_dictionary.Dubsar.PreferencesActivity; import com.dubsar_dictionary.Dubsar.R; /** * Base class for all models. Provides communications * and a basic JSON parsing framework. */ public abstract class Model { public static final String TAG = "Model"; private static HashMap<String, PartOfSpeech> sPosMap = null; private static volatile HttpClient sClient = null; /** * Enumeration to represent parts of speech. */ public enum PartOfSpeech { Unknown, Adjective, Adverb, Conjunction, Interjection, Noun, Preposition, Pronoun, Verb } /** * Convenience method to convert the abbreviated pos name * to an enumerated value. * @param pos a string abbreviation (e.g. "adj" or "n") * @return the corresponding enumerated part of speech (Unknown on failure) */ public static PartOfSpeech partOfSpeechFromPos(String pos) { return getPosMap().get(pos); } /** * Convenience function to return an abbreviated string (e.g., "adj" or * "v") given the corresponding enumerated PartOfSpeech. * @param partOfSpeech an enumerated value * @return the corresponding string value ("unk" on failure) */ public static String posFromPartOfSpeech(PartOfSpeech partOfSpeech) { switch (partOfSpeech) { case Adjective: return "adj"; case Adverb: return "adv"; case Conjunction: return "conj"; case Interjection: return "interj"; case Noun: return "n"; case Preposition: return "prep"; case Pronoun: return "pron"; case Verb: return "v"; default: return "unk"; } } protected static HashMap<String, PartOfSpeech> getPosMap() { if (sPosMap == null) { sPosMap = new HashMap<String, PartOfSpeech>(); sPosMap.put("adj", PartOfSpeech.Adjective); sPosMap.put("adv", PartOfSpeech.Adverb); sPosMap.put("conj", PartOfSpeech.Conjunction); sPosMap.put("interj", PartOfSpeech.Interjection); sPosMap.put("n", PartOfSpeech.Noun); sPosMap.put("prep", PartOfSpeech.Preposition); sPosMap.put("pron", PartOfSpeech.Pronoun); sPosMap.put("v", PartOfSpeech.Verb); } return sPosMap; } private static HashMap<String, String> sMocks = new HashMap<String, String>(); protected HashMap<String, List<List<Object>>> mPointers = null; protected int mPointerCount = 0; /** * This protected member variable is set by child classes. * The full URL is generated by prepending * R.string.dubsar_base_url. */ protected String mPath; /** * This protected member variable is populated by the base * class for the child class to parse in parseData(). */ protected String mData; private static WeakReference<Context> sContextReference = null; private String mUrl = null; private volatile boolean mComplete = false; private volatile boolean mError = false; private String mErrorMessage = null; private PartOfSpeech mPartOfSpeech = PartOfSpeech.Unknown; /** * default constructor */ public Model() { } /** * constructor using PartOfSpeech * @param partOfSpeech the PartOfSpeech */ public Model(PartOfSpeech partOfSpeech) { mPartOfSpeech = partOfSpeech; } /** * Constructor using POS * @param pos the POS */ public Model(String pos) { mPartOfSpeech = partOfSpeechFromPos(pos); } /** * Enumerated part of speech * @return PartOfSpeech enumeration */ public PartOfSpeech getPartOfSpeech() { return mPartOfSpeech; } /** * Set a new part of speech * @param partOfSpeech new part of speech */ public void setPartOfSpeech(PartOfSpeech partOfSpeech) { mPartOfSpeech = partOfSpeech; } /** * Part of speech abbreviation * @return abbreviated form ("adv", "v", etc) */ public final String getPos() { return posFromPartOfSpeech(mPartOfSpeech); } /** * Set a new POS * @param pos the new POS */ public void setPos(String pos) { mPartOfSpeech = partOfSpeechFromPos(pos); } /** * Get the URL associated with this model. * @return this model's full URL */ public final String getUrl() { if (mUrl == null) mUrl = getString(R.string.dubsar_base_url) + mPath; return mUrl; } /** * Has this request finished receiving its response? * @return true if response received; false otherwise */ public boolean isComplete() { return mComplete; } /** * Did this request generate an error? This value is * not meaningful unless isComplete(). * @return true if the request returned an error; false otherwise */ public boolean hasError() { return mError; } /** * If hasError(), this will return a non-null error * message for the end user. * @return a human-readable error message */ public final String getErrorMessage() { return mErrorMessage; } /** * Set the raw data buffer (useful for testing). * @param data a raw JSON buffer to parse */ public void setData(String data) { mData = new String(data); } /** * This model's pointers * @return a map of pointers */ public final HashMap<String, List<List<Object>>> getPointers() { return mPointers; } /** * Get this model's pointer count * @return total number of pointer rows */ public int getPointerCount() { return mPointerCount; } /** * Set the context to use for all model instances when * converting strings, e.g. * @param context the context */ public static void setContext(Context context) { sContextReference = new WeakReference<Context>(context); } /** * The context used for string lookups by the Model class * @return the Context */ public static Context getContext() { return sContextReference != null ? sContextReference.get() : null; } /** * Static convenience function * @param id a numeric string ID (R.string.hello, e.g.) * @return the string value */ public static final String getString(int id) { if (getContext() == null) return null; return getContext().getString(id); } /** * Fetch data synchronously from the server. */ public void load() { try { /* simple HTTP mock for testing */ mData = getMock(); if (mData == null) mData = fetchData(); JSONTokener tokener = new JSONTokener(mData); parseData(tokener.nextValue()); } /* JSONException, ClientProtocolException and IOException * For now (and perhaps indefinitely), we handle them all alike. */ catch (Exception e) { mError = true; mErrorMessage = e.getMessage(); Log.wtf(TAG, e); } mComplete = true; } /** * Must be overridden by child classes to parse the * JSON payload in mData. * @param jsonResponse an object, typically a JSONArray or a JSONObject * @throws JSONException if the tokener encounters a parsing error */ public abstract void parseData(Object jsonResponse) throws JSONException; /** * Add a JSON mock for testing. If a mock is present, its value is * returned to the model instead of requesting data from the server. * @param path a path to mock * @param json the JSON response to return for this path instead of querying the server */ public static void addMock(String path, String json) { sMocks.put(path, json); } /** * Get the JSON mock for this path (or null) * @param path a path to check * @return the associated JSON mock */ public static final String getMock(String path) { return sMocks.get(path); } /** * Get this model's mock (if any) * @return the JSON mock associated with this model's path (null if none) */ public final String getMock() { return getMock(mPath); } /** * Create or return the common HttpClient used by all model requests. * @return the HttpClient */ protected static HttpClient getClient() { if (sClient == null) { SharedPreferences preferences = getContext() .getSharedPreferences(PreferencesActivity.DUBSAR_PREFERENCES, Context.MODE_PRIVATE); String host = preferences.getString(PreferencesActivity.HTTP_PROXY_HOST, null); int port = preferences.getInt(PreferencesActivity.HTTP_PROXY_PORT, 0); // creates the client setProxy(host, port); } return sClient; } private static HttpClient newClient() { String userAgent = getString(R.string.user_agent); userAgent += " (" + getContext().getString(R.string.android_version, new Object[] { Build.VERSION.RELEASE }); userAgent += "; " + getContext().getString(R.string.build, new Object[] { Build.DISPLAY }); userAgent += ")"; HttpClient client = AndroidHttpClient.newInstance(userAgent, getContext()); HttpClientParams.setRedirecting(client.getParams(), true); return client; } /** * Change the proxy settings for the client. Creates the client if it doesn't * exist yet. * @param host * @param port */ public static void setProxy(String host, int port) { if (sClient == null) sClient = newClient(); if (host != null && host.length() > 0 && port > 0) { Log.d(TAG, "HTTP proxy setting is " + host + ":" + port); HttpHost httpHost = new HttpHost(host, port); sClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, httpHost); } else { Log.d(TAG, "No HTTP proxy set"); // prob. no point to this, since we've just created a new client with default settings sClient.getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY); } } /** * Retrieve JSON data for this model instance from the server. * @return the JSON payload * @throws IOException in case of communication error */ protected String fetchData() throws IOException { ResponseHandler<String> handler = new BasicResponseHandler(); // DEBT: Take from strings file or constants Header header = new BasicHeader("Accept", "application/json"); HttpGet request = new HttpGet(getUrl()); request.addHeader(header); HttpClient client = getClient(); String response = null; try { response = client.execute(request, handler); } catch (ConnectionClosedException e) { Log.d(TAG, e.getMessage()); sClient = null; client = getClient(); response = client.execute(request, handler); // If it throws again, let it go (consider it a failure) } return response; } protected void parsePointers(JSONArray pointers) throws JSONException { mPointers = new HashMap<String, List<List<Object>>>(); mPointerCount = pointers.length(); for (int j = 0; j < pointers.length(); ++j) { JSONArray _pointer = pointers.getJSONArray(j); String ptype = _pointer.getString(0); String targetType = _pointer.getString(1); int targetId = _pointer.getInt(2); String targetText = _pointer.getString(3); String targetGloss = _pointer.getString(4); List<List<Object>> pointersByType = mPointers.get(ptype); if (pointersByType == null) { pointersByType = new ArrayList<List<Object>>(); mPointers.put(ptype, pointersByType); } ArrayList<Object> pointer = new ArrayList<Object>(); pointer.add(targetType); pointer.add(Integer.valueOf(targetId)); pointer.add(targetText); pointer.add(targetGloss); pointersByType.add(pointer); } } protected void setErrorMessage(String message) { mErrorMessage = message; mError = true; } }