/* * Copyright 2011 Austin Wagner * * This file is part of Mobile Shuttle Tracker. * * Mobile Shuttle Tracker 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. * * Mobile Shuttle Tracker 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 Mobile Shuttle Tracker. If not, see <http://www.gnu.org/licenses/>. * */ package com.abstractedsheep.shuttletracker; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ByteArrayInputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import com.abstractedsheep.shuttletrackerworld.Coordinate; import com.abstractedsheep.shuttletrackerworld.Netlink; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.json.JSONTokener; import org.json.JSONArray; import com.abstractedsheep.shuttletracker.json.MapJsonInputToArray; import com.abstractedsheep.shuttletracker.json.VehicleArray; import com.abstractedsheep.shuttletracker.json.VehicleJson; import com.abstractedsheep.shuttletracker.sql.DatabaseHelper; import com.abstractedsheep.shuttletrackerworld.World; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.SystemClock; import android.preference.PreferenceManager; import android.util.Log; public class ShuttleDataService implements OnSharedPreferenceChangeListener { private final ObjectMapper mapper = new ObjectMapper(); private final Set<IShuttleServiceCallback> callbacks = new HashSet<IShuttleServiceCallback>(); public final AtomicBoolean active = new AtomicBoolean(true); private World world; private boolean informedNoConnection = false; private Context ctx; private final AtomicInteger updateRate = new AtomicInteger(5000); // Private constructor prevents instantiation from other classes private ShuttleDataService() { } /** * SingletonHolder is loaded on the first execution of Singleton.getInstance() * or the first access to SingletonHolder.INSTANCE, not before. */ private static class SingletonHolder { public static final ShuttleDataService INSTANCE = new ShuttleDataService(); } public static ShuttleDataService getInstance() { return SingletonHolder.INSTANCE; } public void setApplicationContext(Context context) { ctx = context; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); prefs.registerOnSharedPreferenceChangeListener(this); updateRate.set(prefs.getInt(TrackerPreferences.UPDATE_RATE, 5000)); } /** * Uses the Jackson object mapper to read the JSON from a URL into a Java Bean. * * @param url The URL to retrieve the JSON from. * @param generic The class type of the JSON in the form of a Java Bean. * @return The parsed JSON in a new instance of the Java Bean. */ public <T> T parseJson(String url, Class<T> generic) { T parsedClass = null; try { URL jsonUrl = new URL(url); URLConnection jsonConnection = jsonUrl.openConnection(); parsedClass = mapper.readValue(jsonConnection.getInputStream(), generic); informedNoConnection = false; } catch (JsonParseException e) { Log.w("Tracker", "Error Parsing URL: " + url); Log.w("Tracker", "Data type: " + generic.getName()); e.printStackTrace(); } catch (JsonMappingException e) { Log.w("Tracker", "Error Parsing URL: " + url); Log.w("Tracker", "Data type: " + generic.getName()); e.printStackTrace(); } catch (IOException e) { if (!informedNoConnection) { informedNoConnection = true; notifyError(IShuttleServiceCallback.NO_CONNECTION_ERROR); } e.printStackTrace(); } return parsedClass; } public String convertStreamToString(InputStream is) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line); } is.close(); return sb.toString(); } /** * Uses the Jackson object mapper to read the JSON from a URL into a Java Array. * * @param url The URL to retrieve the JSON from. * @param generic The class type of the JSON in the form of a Java Bean. * @return The parsed JSON in a new instance of the Java Array. */ public <T> ArrayList<T> parseJsonArray(String url, Class<T> generic) { ArrayList<T> parsedArray = new ArrayList<T>(); try { URL jsonUrl = new URL(url); URLConnection jsonConnection = jsonUrl.openConnection(); JSONTokener tokener = new JSONTokener(convertStreamToString(jsonConnection.getInputStream())); JSONArray jsonArray = new JSONArray(tokener); T temp; InputStream instream; for(int x = 0; x< jsonArray.length(); ++x){ instream = new ByteArrayInputStream(jsonArray.get(x).toString().getBytes()); temp = mapper.readValue(instream, generic); parsedArray.add(temp); } informedNoConnection = false; } catch (JsonParseException e) { Log.w("Tracker", "Error Parsing URL: " + url); Log.w("Tracker", "Data type: " + generic.getName()); e.printStackTrace(); } catch (JsonMappingException e) { Log.w("Tracker", "Error Parsing URL: " + url); Log.w("Tracker", "Data type: " + generic.getName()); e.printStackTrace(); } catch (IOException e) { if (!informedNoConnection) { informedNoConnection = true; notifyError(IShuttleServiceCallback.NO_CONNECTION_ERROR); } e.printStackTrace(); } catch (Exception e){ Log.w("Tracker", e); } return parsedArray; } public final Runnable updateRoutes = new Runnable() { public void run() { synchronized (this) { Netlink tempRoutes = null; DatabaseHelper db = new DatabaseHelper(ctx); while (tempRoutes == null) { if (db.hasRoutes()) { world = World.generateWorld(db.getRoutes()); notifyRoutesUpdated(); } else { tempRoutes = parseJson("http://shuttles.rpi.edu/displays/netlink.js", Netlink.class); if (tempRoutes != null) { world = World.generateWorld(tempRoutes); db.putRoutes(tempRoutes); notifyRoutesUpdated(); } } SystemClock.sleep(5000); } db.close(); } } }; public final Runnable updateShuttles = new Runnable() { public void run() { while (active.get()) { synchronized (this) { ArrayList<MapJsonInputToArray> tempVehicles = parseJsonArray("http://shuttles.rpi.edu/vehicles/current.js", MapJsonInputToArray.class); for (MapJsonInputToArray v : tempVehicles) { world.addOrUpdateShuttle(v.vehicle.getShuttle_id(), new Coordinate((int)(v.vehicle.getLatitude() * 1e6), (int)(v.vehicle.getLongitude() * 1e6)), v.vehicle.getName(), v.vehicle.getHeading(), v.vehicle.getCardinal_point(), v.vehicle.getSpeed(), v.vehicle.getRoute_id()); } if (tempVehicles != null) { notifyShuttlesUpdated(); } } SystemClock.sleep(updateRate.get()); } } }; public World getWorld() { return world; } private synchronized void notifyShuttlesUpdated() { for (IShuttleServiceCallback c : callbacks) { c.dataUpdated(world); } } private synchronized void notifyRoutesUpdated() { for (IShuttleServiceCallback c : callbacks) { c.routesUpdated(world); } } private synchronized void notifyError(int errorCode) { for (IShuttleServiceCallback c : callbacks) { c.dataServiceError(errorCode); } } public synchronized void registerCallback(IShuttleServiceCallback callback) { callbacks.add(callback); callback.dataUpdated(world); callback.routesUpdated(world); } public synchronized void unregisterCallback(IShuttleServiceCallback callback) { callbacks.remove(callback); } public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { updateRate.set(Integer.parseInt(sharedPreferences.getString(TrackerPreferences.UPDATE_RATE, "5000"))); } }