Java tutorial
/** * Copyright 2012 Scott Weeden-Moody * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lillicoder.newsblurry.feeds; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import com.lillicoder.newsblurry.R; /** * Utility class that can generate {@link Feed}. * @author lillicoder */ public class FeedParser { private static final String TAG = FeedParser.class.getSimpleName(); private static final String JSON_KEY_FAVICON = "favicon"; private static final String JSON_KEY_FEED_ID = "id"; private static final String JSON_KEY_FEED_TITLE = "feed_title"; private static final String JSON_KEY_FEEDS = "feeds"; private static final String JSON_KEY_FOLDERS = "folders"; private static final String JSON_KEY_UNREAD_LIKE_COUNT = "ps"; private static final String JSON_KEY_UNREAD_NEUTRAL_COUNT = "nt"; private static final String JSON_KEY_UNREAD_DISLIKE_COUNT = "ng"; private static final String EXCEPTION_NULL_CONSTRUCTOR_CONTEXT = "The given context must not be null."; private static final String WARNING_FAILED_TO_DECODE_FAVICON = "Failed to decode favicon, defaulting to no favicon."; private static final String WARNING_FAILED_TO_PARSE_FOLDER_JSON = "Failed to parse folder JSON, JSON format is invalid (bad entries or no mappings)."; /** * Default drawable resource for feed favicons. */ private static final int DEFAULT_FEED_FAVICON_RESOURCE = R.drawable.ic_feed_favicon; /** * ID value for a child folder in a folder JSON node. */ private static final int FOLDER_ID = -1; private Context _context; public FeedParser(Context context) { if (context == null) throw new IllegalArgumentException(EXCEPTION_NULL_CONSTRUCTOR_CONTEXT); this.setContext(context); } /** * Decodes the given base-64 favicon string into a {@link Favicon}. * @param encodedFavicon Base-64 encoded favicon to decode. * @return Decoded {@link Favicon}, * <code>null</code> if the encoded string could not be decoded. */ private Favicon decodeFavicon(String encodedFavicon) { Favicon favicon = null; if (!TextUtils.isEmpty(encodedFavicon)) { try { byte[] rawFavicon = Base64.decode(encodedFavicon, Base64.DEFAULT); Bitmap faviconImage = BitmapFactory.decodeByteArray(rawFavicon, 0, rawFavicon.length); if (faviconImage != null) favicon = new Favicon(faviconImage); } catch (IllegalArgumentException e) { Log.w(TAG, WARNING_FAILED_TO_DECODE_FAVICON, e); } } return favicon; } /** * Gets the default {@link Favicon} for feeds. * @return Default feed {@link Favicon}. */ private Favicon getDefaultFeedFavicon() { Favicon defaultFavicon = null; Context context = this.getContext(); Resources resources = context.getResources(); Bitmap defaultFaviconImage = BitmapFactory.decodeResource(resources, DEFAULT_FEED_FAVICON_RESOURCE); if (defaultFaviconImage != null) defaultFavicon = new Favicon(defaultFaviconImage); return defaultFavicon; } /** * Parses the given feed {@link JSONObject} into a {@link Feed} instance. * @param feedJson Feed {@link JSONObject} to parse. * @return Parsed {@link Feed} instance. * @throws JSONException Thrown if expected feed value are not present in the given feed JSON. */ private Feed parseFeed(JSONObject feedJson) throws JSONException { int id = feedJson.getInt(JSON_KEY_FEED_ID); String title = feedJson.getString(JSON_KEY_FEED_TITLE); String encodedFavicon = feedJson.getString(JSON_KEY_FAVICON); Favicon favicon = this.decodeFavicon(encodedFavicon); if (favicon == null) // Favicon failed to parse, use default feed favicon. favicon = this.getDefaultFeedFavicon(); int unreadDislike = feedJson.getInt(JSON_KEY_UNREAD_DISLIKE_COUNT); int unreadNeutral = feedJson.getInt(JSON_KEY_UNREAD_NEUTRAL_COUNT); int unreadLike = feedJson.getInt(JSON_KEY_UNREAD_LIKE_COUNT); UnreadCounts counts = new UnreadCounts(unreadDislike, unreadNeutral, unreadLike); return new Feed(id, title, favicon, counts); } /** * Parses the given feeds {@link JSONArray} into a map of feeds with each * entry keyed by feed ID. * @param feedsRootJson Root feeds {@link JSONObject} to parse. * @return {@link Map} of {@link Feed} keyed by feed ID. * @throws JSONException Thrown when the feeds JSON array cannot be parsed. */ public Map<Integer, Feed> parseFeedsById(JSONArray feedsArrayJson) throws JSONException { Map<Integer, Feed> feedsById = new HashMap<Integer, Feed>(); if (feedsArrayJson != null) { // Get each array entry and place the feed into the map. for (int index = 0; index < feedsArrayJson.length(); index++) { JSONObject feedJson = feedsArrayJson.getJSONObject(index); Feed feed = this.parseFeed(feedJson); feedsById.put(feed.getId(), feed); } } return feedsById; } /** * Parses the given feeds root {@link JSONObject} into a map of feeds * with each entry keyed by feed ID. * @param JSONObject feedsRootJson Feeds root {@link JSONObject}. * @return {@link Map} of {@link Feed} keyed by feed ID. * @throws JSONException Thrown when the feeds root JSON node cannot be parsed. */ public Map<Integer, Feed> parseFeedsById(JSONObject feedsRootJson) throws JSONException { Map<Integer, Feed> feedsById = new HashMap<Integer, Feed>(); // Convert feeds node into an array of elements // using the child node's names. JSONObject feedsJson = feedsRootJson.getJSONObject(JSON_KEY_FEEDS); JSONArray feedsArrayJson = feedsJson.toJSONArray(feedsJson.names()); feedsById = parseFeedsById(feedsArrayJson); return feedsById; } /** * Parses the given root feeds {@link JSONObject} into a collection of * {@link IFeed} populated with all child feeds and folders. * Any given folder may contain both {@link FeedFolder} and {@link Feed}. * @param feedsRootJson Root feeds {@link JSONObject} to parse. * @return {@link List} of parsed {@link IFeed}. * @throws JSONException Thrown when given root feeds JSON node has an invalid format * or is missing feeds/folders data and cannot reliably be parsed. */ public List<IFeed> parseFeeds(JSONObject feedsRootJson) throws JSONException { List<IFeed> rootFeeds = new ArrayList<IFeed>(); // Parse folder structure. FeedCache cache = new FeedCache(); JSONArray foldersArray = feedsRootJson.getJSONArray(JSON_KEY_FOLDERS); for (int index = 0; index < foldersArray.length(); index++) { try { Object objectJson = foldersArray.get(index); if (objectJson instanceof JSONObject) { JSONObject folderJson = foldersArray.getJSONObject(index); FeedFolder folder = this.parseFeedFolder(folderJson, null); if (folder != null) rootFeeds.add(folder); } else { Integer feedId = (Integer) objectJson; Feed feed = cache.getFeed(feedId); if (feed != null) rootFeeds.add(feed); } } catch (Exception e) { // Occurs when folder JSON has an invalid format of some kind, // we should just skip this folder and move on. Log.w(TAG, WARNING_FAILED_TO_PARSE_FOLDER_JSON, e); } } return rootFeeds; } /** * Parses the given folder {@link JSONObject} into a {@link FeedFolder} instance. * If the given parent folder paramter is not null, that folder will be set as the * parsed folder's parent. * @param folderJson Folder {@link JSONObject} to parse. * @param parentFolder Optional parent {@link FeedFolder}. If this is not <code>null</code>, * the parsed folder will set its parent to this parameter. * @return Parsed {@link FeedFolder}. * @throws JSONException Thrown when the folder JSON has an invalid format and cannot be parsed. * @throws NullPointerException Thrown when the folder JSON has no mappings. */ private FeedFolder parseFeedFolder(JSONObject folderJson, FeedFolder parentFolder) throws JSONException, NullPointerException { // Pull the given JSON node's folder name from the // names array. JSONArray folderNameJson = folderJson.names(); String folderName = folderNameJson.getString(0); // Generate folder instance with retrieved title. FeedFolder folder; if (parentFolder != null) folder = new FeedFolder(folderName, parentFolder); else folder = new FeedFolder(folderName); // Get folder contents and iterate. FeedCache cache = new FeedCache(); JSONArray folderContents = folderJson.getJSONArray(folderName); for (int index = 0; index < folderContents.length(); index++) { int feedId = folderContents.optInt(index, FOLDER_ID); if (feedId == FOLDER_ID) { // Child folder, parse folder. JSONObject childFolderJson = folderContents.getJSONObject(index); FeedFolder childFolder = this.parseFeedFolder(childFolderJson, folder); if (childFolder != null) folder.addChildFolder(childFolder); } else { // Normal feed, set ID. Feed feed = cache.getFeed(feedId); if (feed != null) folder.addChildFeed(feed); } } return folder; } /** * Gets the {@link Context} for this parser. * @return Parser {@link Context}. */ private Context getContext() { return this._context; } /** * Sets the {@link Context} for this parser. * @param context {@link Context} to set. */ private void setContext(Context context) { this._context = context; } }