Java tutorial
/* * Copyright (C) 2008 Google Inc. * Copyright (C) 2010 Garen J. Torikian * * 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 biz.varkon.shelvesom.provider.comics; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpGet; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.provider.BaseColumns; import biz.varkon.shelvesom.base.BaseItem; import biz.varkon.shelvesom.base.BaseItem.ImageSize; import biz.varkon.shelvesom.server.CVInfo; import biz.varkon.shelvesom.util.Entities; import biz.varkon.shelvesom.util.IOUtilities; import biz.varkon.shelvesom.util.IOUtilities.inputTypes; import biz.varkon.shelvesom.util.ImageUtilities; import biz.varkon.shelvesom.util.Preferences; import biz.varkon.shelvesom.util.TextUtilities; /** * Utility class to load comics from a comics store. */ public class ComicsStore extends CVInfo { static final String LOG_TAG = "ComicsStore"; public static class Comic extends BaseItem implements Parcelable, BaseColumns { public static final Uri CONTENT_URI = Uri.parse("content://ComicsProvider/comics"); String mAuthors = null; String mDescriptions; Date mPublicationDate; List<String> mArtists; List<String> mCharacters; String mIssueNumber; private CVImageLoader mLoader; Map<ImageSize, String> mImages; @Override public String getImageUrl(ImageSize size) { String url = mImages.get(size); if (TextUtilities.isEmpty(url)) return null; else return TextUtilities.unprotectString(url); } Comic() { this("", null); } Comic(String storePrefix, CVImageLoader loader) { mStorePrefix = CVInfo.NAME; mLoader = loader; mImages = new HashMap<ImageSize, String>(6); mDescriptions = new String(); mTags = new ArrayList<String>(1); mArtists = new ArrayList<String>(1); mCharacters = new ArrayList<String>(1); } private Comic(Parcel in) { mIsbn = in.readString(); mEan = in.readString(); mInternalId = in.readString(); mTitle = in.readString(); mTags = new ArrayList<String>(1); } public String getDescriptions() { return mDescriptions; } public String getAuthors() { if (!TextUtilities.isEmpty(mAuthors)) return mAuthors; else return ""; } public Date getPublicationDate() { return mPublicationDate; } public List<String> getCharacters() { return mCharacters; } public List<String> getArtists() { return mArtists; } public String getIssueNumber() { return mIssueNumber; } @Override public Bitmap loadCover(ImageSize size) { String url = mImages.get(size); if (TextUtilities.isEmpty(url)) { url = mImages.get(ImageSize.MEDIUM); if (TextUtilities.isEmpty(url)) return null; } final ImageUtilities.ExpiringBitmap expiring; if (mLoader == null) { expiring = ImageUtilities.load(url); } else { expiring = mLoader.load(url); } mLastModified = expiring.lastModified; return expiring.bitmap; } public ContentValues getContentValues() { final SimpleDateFormat format = new SimpleDateFormat("MMMM yyyy"); final ContentValues values = new ContentValues(); values.put(INTERNAL_ID, CVInfo.NAME + mInternalId); values.put(EAN, mEan); values.put(TITLE, mTitle); values.put(AUTHORS, mAuthors); values.put(REVIEWS, mDescriptions); if (mLastModified != null) { values.put(LAST_MODIFIED, mLastModified.getTimeInMillis()); } values.put(PUBLICATION, mPublicationDate != null ? format.format(mPublicationDate) : ""); values.put(DETAILS_URL, mDetailsUrl); final int density = Preferences.getDPI(); switch (density) { case 320: values.put(TINY_URL, TextUtilities.protectString(mImages.get(ImageSize.LARGE))); break; case 240: values.put(TINY_URL, TextUtilities.protectString(mImages.get(ImageSize.MEDIUM))); break; case 120: values.put(TINY_URL, TextUtilities.protectString(mImages.get(ImageSize.THUMBNAIL))); break; case 160: default: values.put(TINY_URL, TextUtilities.protectString(mImages.get(ImageSize.TINY))); break; } values.put(CHARACTERS, TextUtilities.join(mCharacters, ", ")); values.put(ARTISTS, TextUtilities.join(mArtists, ", ")); values.put(ISSUE_NUMBER, mIssueNumber); values.put(RETAIL_PRICE, mRetailPrice); // GJT: Added these, for more data values.put(RATING, mRating); values.put(LOANED_TO, mLoanedTo); if (mLoanDate != null) { values.put(LOAN_DATE, Preferences.getDateFormat().format(mLoanDate)); } if (mWishlistDate != null) { values.put(WISHLIST_DATE, Preferences.getDateFormat().format(mWishlistDate)); } values.put(EVENT_ID, mEventId); values.put(NOTES, mNotes); values.put(QUANTITY, mQuantity); return values; } public static Comic fromCursor(Cursor c) { final Comic comic = new Comic(); comic.mInternalId = c.getString(c.getColumnIndexOrThrow(INTERNAL_ID)).substring(CVInfo.NAME.length()); comic.mEan = c.getString(c.getColumnIndexOrThrow(EAN)); comic.mTitle = c.getString(c.getColumnIndexOrThrow(TITLE)); comic.mAuthors = c.getString(c.getColumnIndexOrThrow(AUTHORS)); comic.mDescriptions = c.getString(c.getColumnIndexOrThrow(REVIEWS)); final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(c.getLong(c.getColumnIndexOrThrow(LAST_MODIFIED))); comic.mLastModified = calendar; try { Collections.addAll(comic.mTags, c.getString(c.getColumnIndexOrThrow(TAGS)).split(", ")); } catch (Exception e) { // GJT: Ignore, probably null } final SimpleDateFormat format = new SimpleDateFormat("MMMM yyyy"); try { comic.mPublicationDate = format.parse(c.getString(c.getColumnIndexOrThrow(PUBLICATION))); } catch (ParseException e) { // Ignore } comic.mDetailsUrl = c.getString(c.getColumnIndexOrThrow(DETAILS_URL)); String tiny_url = c.getString(c.getColumnIndexOrThrow(TINY_URL)); if (tiny_url != null) { tiny_url = TextUtilities.unprotectString(c.getString(c.getColumnIndexOrThrow(TINY_URL))); final int density = Preferences.getDPI(); switch (density) { case 320: comic.mImages.put(ImageSize.LARGE, tiny_url); break; case 240: comic.mImages.put(ImageSize.MEDIUM, tiny_url); break; case 120: comic.mImages.put(ImageSize.THUMBNAIL, tiny_url); break; case 160: default: comic.mImages.put(ImageSize.TINY, tiny_url); break; } } Collections.addAll(comic.mCharacters, c.getString(c.getColumnIndexOrThrow(CHARACTERS)).split(", ")); Collections.addAll(comic.mArtists, c.getString(c.getColumnIndexOrThrow(ARTISTS)).split(", ")); comic.mIssueNumber = c.getString(c.getColumnIndex(ISSUE_NUMBER)); comic.mRetailPrice = c.getString(c.getColumnIndexOrThrow(RETAIL_PRICE)); comic.mRating = c.getInt(c.getColumnIndexOrThrow(RATING)); comic.mLoanedTo = c.getString(c.getColumnIndex(LOANED_TO)); String loanDate = c.getString(c.getColumnIndexOrThrow(LOAN_DATE)); if (loanDate != null) { try { comic.mLoanDate = Preferences.getDateFormat().parse(loanDate); } catch (ParseException e) { // Ignore } } String wishlistDate = c.getString(c.getColumnIndexOrThrow(WISHLIST_DATE)); if (wishlistDate != null) { try { comic.mWishlistDate = Preferences.getDateFormat().parse(wishlistDate); } catch (ParseException e) { // Ignore } } comic.mEventId = c.getInt(c.getColumnIndexOrThrow(EVENT_ID)); comic.mNotes = c.getString(c.getColumnIndexOrThrow(NOTES)); comic.mQuantity = c.getString(c.getColumnIndex(QUANTITY)); return comic; } @Override public String toString() { return "Comic[EAN=" + mEan + "]"; } public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeString(mEan); dest.writeString(mInternalId); dest.writeString(mTitle); dest.writeStringList(mTags); } public static final Creator<Comic> CREATOR = new Creator<Comic>() { public Comic createFromParcel(Parcel in) { return new Comic(in); } public Comic[] newArray(int size) { return new Comic[size]; } }; } public ComicsStore() { mStoreName = "CVComics"; mStoreLabel = "CV"; mHost = CVInfo.REST_HOST; mLoader = new CVImageLoader(); } /** * Finds the comic with the specified id. * * @param id * The id of the comic to find (ISBN-10, ISBN-13, etc.) * @param type * * @return A Comic instance if the comic was found or null otherwise. */ public Comic findComic(String id, final IOUtilities.inputTypes mSavedImportType, Context context) { final Uri.Builder uri = CVInfo.buildFindQuery(id); final String uriBuild = uri.build().toString(); android.util.Log.i(LOG_TAG, "Looking up comic #" + id); HttpGet get = new HttpGet(uriBuild); Comic comic = createComic(); comic = findComicLookup(get, comic, mSavedImportType, id); if (comic != null) { return comic; } else return null; } private Comic findComicLookup(HttpGet get, final Comic comic, final inputTypes mSavedImportType, String id) { final boolean[] result = new boolean[1]; try { executeRequest(new HttpHost(mHost, 80, "http"), get, new ResponseHandler() { public void handleResponse(InputStream in) throws IOException { parseResponse(in, new ResponseParser() { public void parseResponse(XmlPullParser parser) throws XmlPullParserException, IOException { result[0] = parseComic(parser, comic); } }, mSavedImportType); } }); } catch (IOException e) { android.util.Log.e(LOG_TAG, "Could not find " + mSavedImportType + " item with ID " + id); } return result[0] ? comic : null; } /** * Searches for comics that match the provided query. * * @param query * The free form query used to search for comics. * * @return A list of Comic instances if query was successful or null * otherwise. */ public ArrayList<Comic> searchComics(String query, String page, final ComicSearchListener listener, Context context) { final Uri.Builder uri = CVInfo.buildSearchQuery(query, page); android.util.Log.i(LOG_TAG, "Looking up comics: " + query + " @offset: " + page); final HttpGet get = new HttpGet(uri.build().toString()); final ArrayList<Comic> comics = new ArrayList<Comic>(10); try { executeRequest(new HttpHost(mHost, 80, "http"), get, new ResponseHandler() { public void handleResponse(InputStream in) throws IOException { parseResponse(in, new ResponseParser() { public void parseResponse(XmlPullParser parser) throws XmlPullParserException, IOException { parseComics(parser, comics, listener); } }, null); } }); return comics; } catch (IOException e) { android.util.Log.e(LOG_TAG, "Could not perform search with query: " + query, e); } return null; } /** * Parses a comic from the XML input stream. * * @param parser * The XML parser to use to parse the comic. * @param comic * The comic object to put the parsed data in. * * @return True if the comic could correctly be parsed, false otherwise. */ boolean parseComic(XmlPullParser parser, Comic comic) throws XmlPullParserException, IOException { int type; String name; final int depth = parser.getDepth(); String publishMonth = ""; String publishYear = ""; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } name = parser.getName(); // getDepth() is to remove bug when trying to add comic if (RESPONSE_TAG_ID.equals(name) && parser.getDepth() <= 4) { if (parser.next() == XmlPullParser.TEXT) { if (comic.mInternalId == null) { comic.mInternalId = parser.getText(); comic.mEan = comic.mInternalId; } } } else if (RESPONSE_TAG_PERSON.equals(name)) { comic.mArtists.add(parseArtists(parser)); } else if (RESPONSE_TAG_IMAGESET.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { final String imgURL = parser.getText(); comic.mImages.put(ImageSize.THUMBNAIL, imgURL); comic.mImages.put(ImageSize.TINY, imgURL); comic.mImages.put(ImageSize.MEDIUM, imgURL); comic.mImages.put(ImageSize.LARGE, imgURL); } } else if (RESPONSE_TAG_DESCRIPTION.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { Entities e = new Entities(); String content = parser.getText(); comic.mDescriptions = e.unescape(content.replaceAll("\\<.*?>", "")); } } else if (RESPONSE_TAG_DETAILPAGEURL.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { if (TextUtilities.isEmpty(comic.mDetailsUrl) || parser.getDepth() == 3) comic.mDetailsUrl = parser.getText(); } } else if (RESPONSE_TAG_MONTHPUBLISHED.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { publishMonth = parser.getText(); } } else if (RESPONSE_TAG_YEARPUBLISHED.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { publishYear = parser.getText(); final SimpleDateFormat monthAndYearFormat = new SimpleDateFormat("yyyy-MM"); final SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); try { if (!TextUtilities.isEmpty(publishMonth) && !TextUtilities.isEmpty(publishYear)) comic.mPublicationDate = monthAndYearFormat.parse(publishYear + "-" + publishMonth); else comic.mPublicationDate = yearFormat.parse(publishYear); } catch (ParseException e) { e.printStackTrace(); } } } else if (RESPONSE_TAG_CHARACTER.equals(name)) { if (TextUtilities.isEmpty(comic.mAuthors)) { String[] charactersResult = parseCharacters(parser, true).split("~"); comic.mCharacters.add(charactersResult[0]); comic.mAuthors = charactersResult[1]; } else { comic.mCharacters.add(parseCharacters(parser, false)); } } else if (RESPONSE_TAG_ISSUENUMBER.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { comic.mIssueNumber = parser.getText(); } } else if (RESPONSE_TAG_NAME.equals(name)) { if (parser.next() == XmlPullParser.TEXT) { if (TextUtilities.isEmpty(comic.mTitle) || parser.getDepth() == 3) comic.mTitle = parser.getText(); } } } return true; } /** * Creates an instance of * {@link biz.varkon.shelvesom.provider.comics.ComicsStore.Comic} with this * comic store's name. * * @return A new instance of Comic. */ Comic createComic() { return new Comic(getName(), mLoader); } private void parseComics(XmlPullParser parser, ArrayList<Comic> comics, ComicSearchListener listener) throws IOException, XmlPullParserException { int type; while ((type = parser.next()) != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } if (findNextItem(parser)) { final Comic comic = createComic(); if (parseComic(parser, comic)) { comics.add(comic); listener.onComicFound(comic, comics); } } } } protected String parseArtists(XmlPullParser parser) throws XmlPullParserException, IOException { String authorName = null; List<String> roles = new ArrayList<String>(); while (true) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { String tag = parser.getName(); if (RESPONSE_TAG_NAME.equals(tag)) { parser.next(); authorName = parser.getText(); } else if (RESPONSE_TAG_ROLE.equals(tag)) { parser.next(); roles.add(TextUtilities.capitalizeString(parser.getText())); } } else if (roles.size() > 0 && eventType == XmlPullParser.END_TAG) { break; } } StringBuilder authorWithRoles = new StringBuilder(); authorWithRoles.append(authorName); authorWithRoles.append(" ("); authorWithRoles.append(TextUtilities.join(roles, ", ")); authorWithRoles.append(")"); return authorWithRoles.toString(); } protected String parseCharacters(XmlPullParser parser, boolean getPublisher) throws XmlPullParserException, IOException { String characterName = null; String ID = null; while (true) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { String tag = parser.getName(); if (RESPONSE_TAG_NAME.equals(tag)) { parser.next(); characterName = parser.getText(); } if (RESPONSE_TAG_ID.equals(tag)) { parser.next(); ID = parser.getText(); } } else if (characterName != null && eventType == XmlPullParser.END_TAG) { break; } } if (getPublisher) return characterName + "~" + sendGetRequest(ID); else return characterName; } public static String sendGetRequest(String id) { String result = null; // Send a GET request to the servlet try { // Send data String urlStr = "http://" + mHost + CVInfo.buildGetPublisherQuery(id).build().toString(); URL url = new URL(urlStr); URLConnection conn = url.openConnection(); // Get the response BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = rd.readLine()) != null) { sb.append(line); } rd.close(); result = sb.toString(); } catch (Exception e) { e.printStackTrace(); } String[] restOfResult = result.split("name\": "); String resultName = restOfResult[1].substring(0, restOfResult[1].indexOf("}")); return resultName.replaceAll("\"", ""); } /** * Interface used to load images with an expiring date. The expiring date is * handled by the image cache to check for updated images from time to time. */ public static interface ImageLoader { /** * Load the specified as a Bitmap and associates an expiring date to it. * * @param url * The URL of the image to load. * * @return The Bitmap decoded from the URL and an expiration date. */ public ImageUtilities.ExpiringBitmap load(String url); } /** * Listener invoked by * {@link biz.varkon.shelvesom.provider.comics.ComicsStore#searchComics(String, biz.varkon.shelvesom.provider.comics.ComicsStore.ComicSearchListener)} * . */ public static interface ComicSearchListener { /** * Invoked whenever a comic was found by the search operation. * * @param comic * The comic yield by the search query. * @param comics * The comics found so far, including <code>comic</code>. */ void onComicFound(Comic comic, ArrayList<Comic> comics); } }