Java tutorial
package edu.mit.mobile.android.locast.data; /* * Copyright (C) 2010 MIT Mobile Experience Lab * * 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. */ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.accounts.Account; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import com.stackoverflow.CollectionUtils; import com.stackoverflow.Predicate; import edu.mit.mobile.android.content.ProviderUtils; import edu.mit.mobile.android.locast.accounts.Authenticator; /** * DB entry for an item that can be tagged. * * @author stevep * */ public abstract class TaggableItem extends JsonSyncableItem { @SuppressWarnings("unused") private static final String TAG = TaggableItem.class.getSimpleName(); public static final String _PRIVACY = "privacy", _AUTHOR = "author", _AUTHOR_URI = "author_uri"; public static final String PRIVACY_PUBLIC = "public", PRIVACY_PROTECTED = "protected", PRIVACY_PRIVATE = "private"; // the ordering of this must match the arrays.xml public static final String[] PRIVACY_LIST = { PRIVACY_PUBLIC, PRIVACY_PRIVATE }; public static final TaggableItemSyncMap SYNC_MAP = new TaggableItemSyncMap(); /** * The name of the server query parameter to filter using tags. */ public static final String SERVER_QUERY_PARAMETER = "tags"; /** * An item that will sync "tags" and "system_tags" fields. * @author steve * */ public static class TaggableItemSyncMap extends JsonSyncableItem.ItemSyncMap { public TaggableItemSyncMap() { super(); put(Tag.PATH, new SyncMapJoiner(new TagSyncField("tags", SyncItem.SYNC_TO), new TagSyncField("system_tags", SYSTEM_PREFIX, SyncItem.SYNC_TO)) { @Override public ContentValues joinContentValues(ContentValues[] cv) { return null; } }); final SyncMap authorSync = new SyncMap(); authorSync.put(_AUTHOR, new SyncFieldMap("display_name", SyncFieldMap.STRING, SyncItem.FLAG_OPTIONAL)); authorSync.put(_AUTHOR_URI, new SyncFieldMap("uri", SyncFieldMap.STRING)); put("_author", new SyncMapChain("author", authorSync, SyncItem.SYNC_FROM)); put(_PRIVACY, new SyncFieldMap("privacy", SyncFieldMap.STRING)); } /** * */ private static final long serialVersionUID = 1L; }; public TaggableItem(Cursor c) { super(c); } @Override public SyncMap getSyncMap() { return SYNC_MAP; } public static class TagSyncField extends SyncCustom { final private String prefix; public TagSyncField(String remoteKey, int flags) { super(remoteKey, flags); prefix = null; } public TagSyncField(String remoteKey, String prefix, int flags) { super(remoteKey, flags); this.prefix = prefix; } @Override public JSONArray toJSON(Context context, Uri localItem, Cursor c, String lProp) throws JSONException { if (localItem == null || context.getContentResolver().getType(localItem).startsWith("vnd.android.cursor.dir")) { return null; } JSONArray jo = null; if (localItem != null) { jo = new JSONArray(getTags(context.getContentResolver(), localItem, prefix)); } return jo; } @Override public ContentValues fromJSON(Context context, Uri localItem, JSONObject item, String lProp) throws JSONException { return null; // this shouldn't be called. } @Override public void onPostSyncItem(Context context, Account account, Uri uri, JSONObject item, boolean updated) throws SyncException, IOException { super.onPostSyncItem(context, account, uri, item, updated); if (updated) { // tags need to be loaded here, as they need a valid localUri in order to save. final JSONArray ja = item.optJSONArray(remoteKey); final List<String> tags = new ArrayList<String>(ja.length()); for (int i = 0; i < ja.length(); i++) { tags.add(ja.optString(i)); } //Log.d(TAG, uri + " has the following "+remoteKey +": "+ tags); TaggableItem.putTags(context.getContentResolver(), uri, tags, prefix); } } } /** * @param c a cursor pointing at an item's row * @return true if the item is editable by the logged-in user. */ public static boolean canEdit(Context context, Cursor c) { final String privacy = c.getString(c.getColumnIndex(_PRIVACY)); final String useruri = Authenticator.getUserUri(context); return privacy == null || useruri == null || useruri.length() == 0 || useruri.equals(c.getString(c.getColumnIndex(_AUTHOR_URI))); } /** * @param c * @return true if the authenticated user can change the item's privacy level. */ public static boolean canChangePrivacyLevel(Context context, Cursor c) { final String useruri = Authenticator.getUserUri(context); return useruri == null || useruri.equals(c.getString(c.getColumnIndex(_AUTHOR_URI))); } /** * @param cr * @return a list of all the tags attached to a given item */ public static Set<String> getTags(ContentResolver cr, Uri item) { return getTags(cr, item, null); } /** * @param cr * @param item * @param prefix * @return a list of all the tags attached to a given item */ public static Set<String> getTags(ContentResolver cr, Uri item, String prefix) { final Cursor tags = cr.query(Uri.withAppendedPath(item, Tag.PATH), Tag.DEFAULT_PROJECTION, null, null, null); final Set<String> tagSet = new HashSet<String>(tags.getCount()); final int tagColumn = tags.getColumnIndex(Tag._NAME); final Predicate<String> predicate = getPrefixPredicate(prefix); for (tags.moveToFirst(); !tags.isAfterLast(); tags.moveToNext()) { final String tag = tags.getString(tagColumn); if (predicate.apply(tag)) { final int separatorIndex = tag.indexOf(PREFIX_SEPARATOR); if (separatorIndex == -1) { tagSet.add(tag); } else { tagSet.add(tag.substring(separatorIndex + 1)); } } } tags.close(); return tagSet; } /** * Sets the tags of the given item. Any existing tags will be deleted. * @param cr * @param item * @param tags */ public static void putTags(ContentResolver cr, Uri item, Collection<String> tags) { putTags(cr, item, tags, null); } public static String CV_TAG_PREFIX = "prefix"; /** * Sets the tags of a given prefix for the given item. Any existing tags using the given prefix will be deleted. * @param cr * @param item * @param tags * @param prefix */ public static void putTags(ContentResolver cr, Uri item, Collection<String> tags, String prefix) { final ContentValues cv = new ContentValues(); cv.put(Tag.PATH, TaggableItem.toListString(addPrefixToTags(prefix, tags))); cv.put(CV_TAG_PREFIX, prefix); cr.update(Uri.withAppendedPath(item, Tag.PATH), cv, null, null); } public static int MAX_POPULAR_TAGS = 10; /** * TODO make this pick the set of tags for a set of content. * * @param cr a content resolver * @return the top MAX_POPULAR_TAGS most popular tags in the set, with the most popular first. */ public static List<String> getPopularTags(ContentResolver cr) { final Map<String, Integer> tagPop = new HashMap<String, Integer>(); final List<String> popTags; final Cursor c = cr.query(Tag.CONTENT_URI, Tag.DEFAULT_PROJECTION, null, null, null); final int tagColumn = c.getColumnIndex(Tag._NAME); for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { final String tag = c.getString(tagColumn); final Integer count = tagPop.get(tag); if (count == null) { tagPop.put(tag, 1); } else { tagPop.put(tag, count + 1); } } c.close(); popTags = new ArrayList<String>(tagPop.keySet()); Collections.sort(popTags, new Comparator<String>() { public int compare(String object1, String object2) { return tagPop.get(object2).compareTo(tagPop.get(object1)); } }); int limit; if (popTags.size() < MAX_POPULAR_TAGS) { limit = popTags.size(); } else { limit = MAX_POPULAR_TAGS; } return popTags.subList(0, limit); } /** * Given a base content URI of a taggable item and a list of tags, constructs a URI * representing all the items of the baseUri that match all the listed tags. * * @param baseUri a content URI of a TaggableItem * @param tags a collection of tags * @return a URI representing all the items that match all the given tags * @see #getTagUri(Uri, Collection) */ public static Uri getTagUri(Uri baseUri, String... tags) { return getTagUri(baseUri, Arrays.asList(tags)); } /** * Given a base content URI of a taggable item and a list of tags, constructs a URI * representing all the items of the baseUri that match all the listed tags. * * @param baseUri a content URI of a TaggableItem * @param tags a collection of tags * @return a URI representing all the items that match all the given tags */ public static Uri getTagUri(Uri baseUri, Collection<String> tags) { if (tags.isEmpty()) { return baseUri; } final List<String> path = baseUri.getPathSegments(); // AUTHORITY/casts/tags if (path.size() >= 2 && Tag.PATH.equals(path.get(path.size() - 1))) { baseUri = ProviderUtils.removeLastPathSegment(baseUri); } return baseUri.buildUpon().appendQueryParameter(SERVER_QUERY_PARAMETER, Tag.toTagQuery(tags)).build(); } /** * @param baseUri * @return the URI of the list of all tags for the given item */ public static Uri getTagListUri(Uri baseUri) { return Uri.withAppendedPath(baseUri, Tag.PATH); } /** * Given a URI that includes a set of tags, parses them out and returns them as a set. * * @param contentUri * @return the set of tags encoded in the URI * @throws IllegalArgumentException * if the URI doesn't contain a tag query string */ public static Set<String> getTagsFromUri(Uri contentUri) throws IllegalArgumentException { final String query = contentUri.getQueryParameter(SERVER_QUERY_PARAMETER); if (query == null) { throw new IllegalArgumentException("uri doesn't contain any tags"); } return Tag.toSet(query); } private final static char PREFIX_SEPARATOR = ':'; public final static String SYSTEM_PREFIX = "system"; // cache predicates so objects don't get created each time a query is made. private static HashMap<String, HasPrefixPredicate> predicates = new HashMap<String, HasPrefixPredicate>(); private static HasPrefixPredicate getPrefixPredicate(String prefix) { if (predicates.containsKey(prefix)) { return predicates.get(prefix); } else { final HasPrefixPredicate predicate = new HasPrefixPredicate(prefix); predicates.put(prefix, predicate); return predicate; } } public static Collection<String> filterTags(String prefix, Collection<String> tags) { final Predicate<String> predicate = getPrefixPredicate(prefix); return CollectionUtils.filter(tags, predicate); } public static void filterTagsInPlace(String prefix, Collection<String> tags) { final Predicate<String> predicate = getPrefixPredicate(prefix); CollectionUtils.filterInPlace(tags, predicate); } private static class HasPrefixPredicate implements Predicate<String> { private final String mPrefix; /** * @param prefix prefix string or null for un-prefixed tags. */ public HasPrefixPredicate(String prefix) { mPrefix = prefix; } public boolean apply(String in) { final int separatorIndex = in.indexOf(PREFIX_SEPARATOR); if (separatorIndex == -1) { // we asked for a prefix, but this contains none. if (mPrefix != null) { return false; // a null prefix was requested, so it's good that we have no separator. } else { return true; } } return in.substring(0, separatorIndex).equals(mPrefix); } } public static String addPrefixToTag(String prefix, String tag) { return prefix + PREFIX_SEPARATOR + tag; } private static Collection<String> addPrefixToTags(String prefix, Collection<String> tags) { if (prefix == null) { return tags; } final ArrayList<String> prefixedTags = new ArrayList<String>(tags.size()); for (final String tag : tags) { prefixedTags.add(addPrefixToTag(prefix, tag)); } return prefixedTags; } /** * Strips prefixes from tags. * * @param tags * @return a list of the tags with any prefix removed. */ public static Set<String> removePrefixesFromTags(Collection<String> tags) { if (tags == null) { return null; } final Set<String> nonPrefixedTags = new HashSet<String>(tags.size()); for (final String tag : tags) { final int sepIndex = tag.indexOf(PREFIX_SEPARATOR); if (sepIndex != -1) { nonPrefixedTags.add(tag.substring(sepIndex + 1)); } else { nonPrefixedTags.add(tag); } } return nonPrefixedTags; } }