package edu.vanderbilt.vm.guide.util; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import com.google.gson.stream.JsonReader; import edu.vanderbilt.vm.guide.annotations.NeedsTesting; import edu.vanderbilt.vm.guide.container.Place; import edu.vanderbilt.vm.guide.db.GuideDBConstants; import edu.vanderbilt.vm.guide.db.GuideDBConstants.NodeTable; import edu.vanderbilt.vm.guide.db.GuideDBConstants.PlaceTable; import edu.vanderbilt.vm.guide.db.GuideDBConstants.TourTable; /** * This class contains a bunch of static helper methods that handle the JSON * data. It provides a nice bridge between the SQLiteDatabase and the JSON * files. * * @author nick */ @NeedsTesting(lastModifiedDate = "12/22/12") public class JsonUtils { private static final int BAD_ID = -1; private static final Logger logger = LoggerFactory.getLogger("util.JsonUtils"); /** * Because this is just a class for static utility methods, this class * should not be instantiated. */ private JsonUtils() { throw new AssertionError("Do not instantiate this class."); } /** * Makes a list of places from a JSON-formatted input stream. * * @param in The InputStream with the JSON-formatted data * @return a list of places created * @throws IOException * @deprecated Use the SQLite database methods instead to read in new places * from an input stream and store them in the database */ @Deprecated public static List<Place> readPlacesFromInputStream(InputStream in) throws IOException { JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); List<Place> places = new ArrayList<Place>(); reader.beginArray(); while (reader.hasNext()) { places.add(readPlace(reader)); } return places; } /** * Reads the next place from a JsonReader. * * @param reader * @return * @throws IOException * @deprecated Use the SQLiteDatabase methods now. */ @Deprecated public static Place readPlace(JsonReader reader) throws IOException { Place.Builder bldr = new Place.Builder(); reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("id")) { bldr.setUniqueId(reader.nextInt()); } else if (name.equals("name")) { bldr.setName(reader.nextString()); } else if (name.equals("category")) { reader.beginArray(); while (reader.hasNext()) { bldr.addCategory(reader.nextString()); } reader.endArray(); } else if (name.equals("hours")) { bldr.setHours(reader.nextString()); } else if (name.equals("placeDescription")) { bldr.setDescription(reader.nextString()); } else if (name.equals("imagePath")) { bldr.setImageLoc(reader.nextString()); } else if (name.equals("videoPath")) { bldr.setVideoLoc(reader.nextString()); } else if (name.equals("audioPath")) { bldr.setAudioLoc(reader.nextString()); } else if (name.equals("latitude")) { bldr.setLatitude(reader.nextDouble()); } else if (name.equals("longitude")) { bldr.setLongitude(reader.nextDouble()); } else { reader.skipValue(); } } reader.endObject(); return bldr.build(); } /** * Adds tuples to a SQLite database from a JSON-formatted input stream. The * table name lets this method determine which type of JSON it is dealing * with, so use GuideDbConstants.PlaceTable.PLACE_TABLE_NAME or * GuideDBConstants.TourTable.TOUR_TABLE_NAME to inform this method of which * type of JSON parsing to do. * * @param tableName Name of table to populate; used to determine how the * JSON is formatted * @param in The InputStream that contains the JSON to read * @param db The opened SQLiteDatabase to be populated * @return The populated database (a reference to db) * @throws IOException */ public static SQLiteDatabase populateDatabaseFromInputStream(String tableName, InputStream in, SQLiteDatabase db) throws IOException { JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); reader.beginArray(); if (tableName.equals(GuideDBConstants.PlaceTable.PLACE_TABLE_NAME)) { while (reader.hasNext()) { insertPlaceTuple(db, reader); } } else if (tableName.equals(GuideDBConstants.TourTable.TOUR_TABLE_NAME)) { while (reader.hasNext()) { insertTourTuple(db, reader); } } else if (tableName.equals(GuideDBConstants.NodeTable.NODE_TABLE_NAME)) { while (reader.hasNext()) { insertNodeTuple(db, reader); } } return db; } /** * Inserts a tuple into the db for the next Place object stored in the * JsonReader's buffer. * * @param db The SQLiteDatabase to insert into * @param reader The reader to read the next Place from * @throws IOException */ private static void insertPlaceTuple(SQLiteDatabase db, JsonReader reader) throws IOException { reader.beginObject(); int id = BAD_ID; ContentValues cv = new ContentValues(); while (reader.hasNext()) { String propertyName = reader.nextName(); if (propertyName.equals("id")) { id = reader.nextInt(); } else if (propertyName.equals("name")) { cv.put(PlaceTable.NAME_COL, reader.nextString()); } else if (propertyName.equals("category")) { reader.beginArray(); StringBuilder categories = new StringBuilder(); while (reader.hasNext()) { categories.append(reader.nextString()); categories.append(','); } // Remove trailing comma categories.deleteCharAt(categories.length() - 1); reader.endArray(); cv.put(PlaceTable.CATEGORY_COL, categories.toString()); } else if (propertyName.equals("hours")) { cv.put(PlaceTable.HOURS_COL, reader.nextString()); } else if (propertyName.equals("placeDescription")) { cv.put(PlaceTable.DESCRIPTION_COL, reader.nextString()); } else if (propertyName.equals("imagePath")) { cv.put(PlaceTable.IMAGE_LOC_COL, reader.nextString()); } else if (propertyName.equals("videoPath")) { cv.put(PlaceTable.VIDEO_LOC_COL, reader.nextString()); } else if (propertyName.equals("audioPath")) { cv.put(PlaceTable.AUDIO_LOC_COL, reader.nextString()); } else if (propertyName.equals("latitude")) { cv.put(PlaceTable.LATITUDE_COL, reader.nextDouble()); } else if (propertyName.equals("longitude")) { cv.put(PlaceTable.LONGITUDE_COL, reader.nextDouble()); } else { reader.skipValue(); logger.warn("JSON has a bad property name"); } } reader.endObject(); if (id == BAD_ID) { logger.warn("Got a place with no ID. Skipping."); return; } cv.put(PlaceTable.ID_COL, id); logger.trace("Inserting cv into places table: {}", cv); db.insert(PlaceTable.PLACE_TABLE_NAME, null, cv); } /** * Inserts a tuple into the db for the next Tour object stored in the * JsonReader's buffer. * * @param db The SQLiteDatabase to insert into * @param reader The reader to read the next Tour from * @throws IOException */ private static void insertTourTuple(SQLiteDatabase db, JsonReader reader) throws IOException { reader.beginObject(); int id = BAD_ID; ContentValues cv = new ContentValues(); while (reader.hasNext()) { String propertyName = reader.nextName(); if (propertyName.equals("id")) { id = reader.nextInt(); } else if (propertyName.equals("name")) { cv.put(TourTable.NAME_COL, reader.nextString()); } else if (propertyName.equals("timeRequired")) { cv.put(TourTable.TIME_REQUIRED_COL, reader.nextString()); } else if (propertyName.equals("placesOnTour")) { reader.beginArray(); StringBuilder placeIds = new StringBuilder(); while (reader.hasNext()) { placeIds.append(reader.nextInt()); placeIds.append(','); } // Remove trailing comma if (placeIds.length() != 0) { placeIds.deleteCharAt(placeIds.length() - 1); } reader.endArray(); cv.put(TourTable.PLACES_ON_TOUR_COL, placeIds.toString()); } else if (propertyName.equals("distance")) { cv.put(TourTable.DISTANCE_COL, reader.nextString()); } else if (propertyName.equals("description")) { cv.put(TourTable.DESCRIPTION_COL, reader.nextString()); } else if (propertyName.equals("iconPath")) { cv.put(TourTable.ICON_LOC_COL, reader.nextString()); } else { reader.skipValue(); logger.warn("JSON has a bad property name"); } } reader.endObject(); if (id == BAD_ID) { logger.warn("Got a tour with no ID. Skipping."); return; } cv.put(TourTable.ID_COL, id); logger.trace("Inserting ContentValues into Tour table: {}", cv); db.insert(TourTable.TOUR_TABLE_NAME, null, cv); } private static void insertNodeTuple(SQLiteDatabase db, JsonReader reader) throws IOException { reader.beginObject(); int id = BAD_ID; ContentValues cv = new ContentValues(); while (reader.hasNext()) { String propertyName = reader.nextName(); if (propertyName.equals("id")) { id = reader.nextInt(); } else if (propertyName.equals("lat")) { cv.put(NodeTable.LAT_COL, reader.nextDouble()); } else if (propertyName.equals("lng")) { cv.put(NodeTable.LON_COL, reader.nextDouble()); } else if (propertyName.equals("neighbors")) { reader.beginArray(); StringBuilder neighborIds = new StringBuilder(); while (reader.hasNext()) { neighborIds.append(reader.nextInt()); neighborIds.append(','); } // Remove trailing comma if (neighborIds.length() != 0) { neighborIds.deleteCharAt(neighborIds.length() - 1); } reader.endArray(); cv.put(NodeTable.NEIGHBOR_COL, neighborIds.toString()); } } reader.endObject(); if (id == BAD_ID) { logger.warn("Got a tour with no ID. Skipping."); return; } cv.put(NodeTable.ID_COL, id); logger.trace("Inserting ContentValues into Node table: {}", cv); db.insert(NodeTable.NODE_TABLE_NAME, null, cv); } }