Java tutorial
/* * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.engagement.logic; import android.content.Context; import com.apptentive.android.sdk.Log; import com.apptentive.android.sdk.model.*; import com.apptentive.android.sdk.storage.*; import com.apptentive.android.sdk.util.Util; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * @author Sky Kelsey */ public class ComparisonPredicate extends Predicate { protected String query; protected List<Condition> conditions; public ComparisonPredicate(String query, Object condition) throws JSONException { this.query = query; conditions = new ArrayList<Condition>(); if (condition instanceof JSONObject) { JSONObject conditionJson = (JSONObject) condition; // This is an object. It may contain multiple comparison operations, so unroll it and add them all in. @SuppressWarnings("unchecked") Iterator<String> it = (Iterator<String>) conditionJson.keys(); while (it.hasNext()) { String conditionKey = it.next(); Operation operation = Operation.valueOf(conditionKey); conditions.add(new Condition(operation, conditionJson.get(conditionKey))); } } else { // If it's a literal, then it has to be an EQUALITY operation. The others are always wrapped in a JSONObject. conditions.add(new Condition(Operation.$eq, condition)); } } /** * Makes sure that if the first parameter is a Double, the second is converted to a Double. If the first parameter is * not a Double, or the second can't be converted to a Double, then simply return the second parameter. * * @return The second parameter, converted to a Double if it is a Number, and the first parameter is a Double. Else * return the second parameter straight away. */ private Object normalize(Object one, Object two) { if (one instanceof Double && two instanceof Number) { return ((Number) two).doubleValue(); } return two; } public Comparable getValue(Context context, String query) { String[] tokens = query.split("/"); QueryType queryType = QueryType.parse(tokens[0]); switch (queryType) { case application_version: return Util.getAppVersionName(context); case application_build: return (double) Util.getAppVersionCode(context); case current_time: return Util.currentTimeSeconds(); case is_update: { ValueSubFilterType subFilterType = ValueSubFilterType.parse(tokens[1]); Boolean isUpdate = false; switch (subFilterType) { case version: isUpdate = VersionHistoryStore.isUpdate(context, VersionHistoryStore.Selector.version); break; case build: isUpdate = VersionHistoryStore.isUpdate(context, VersionHistoryStore.Selector.build); break; default: Log.w("Unsupported sub filter type \"%s\" for query \"%s\"", subFilterType.name(), query); } return isUpdate; } case time_since_install: { ValueSubFilterType subFilterType = ValueSubFilterType.parse(tokens[1]); Double seconds = null; switch (subFilterType) { case total: seconds = VersionHistoryStore.getTimeSinceVersionFirstSeen(context, VersionHistoryStore.Selector.total); break; case version: seconds = VersionHistoryStore.getTimeSinceVersionFirstSeen(context, VersionHistoryStore.Selector.version); break; case build: seconds = VersionHistoryStore.getTimeSinceVersionFirstSeen(context, VersionHistoryStore.Selector.build); break; default: Log.w("Unsupported sub filter type \"%s\" for query \"%s\"", subFilterType.name(), query); } return seconds; } case interactions: // Handled same as interactions below. case code_point: boolean isInteraction = queryType.equals(QueryType.interactions); String name = tokens[1]; ValueFilterType valueFilterType = ValueFilterType.parse(tokens[2]); ValueSubFilterType valueSubFilterType = ValueSubFilterType.parse(tokens[3]); switch (valueFilterType) { case invokes: switch (valueSubFilterType) { case total: // Get total for all versions of the app. return (double) CodePointStore.getTotalInvokes(context, isInteraction, name); case version: String appVersion = Util.getAppVersionName(context); return (double) CodePointStore.getVersionInvokes(context, isInteraction, name, appVersion); case build: String appBuild = String.valueOf(Util.getAppVersionCode(context)); return (double) CodePointStore.getBuildInvokes(context, isInteraction, name, appBuild); case time_ago: double timeAgo = Util.currentTimeSeconds() - CodePointStore.getLastInvoke(context, isInteraction, name); return timeAgo; default: break; } break; default: break; } case person: { String key = tokens[1]; boolean queryCustomData = false; if (key.equals("custom_data")) { queryCustomData = true; key = tokens[2]; } Person person = PersonManager.getStoredPerson(context); if (queryCustomData) { CustomData customData = person.getCustomData(); if (customData != null) { return (Comparable) customData.opt(key); } } else { return (Comparable) person.opt(key); } } case device: { String key = tokens[1]; boolean queryCustomData = false; if (key.equals(Device.KEY_CUSTOM_DATA)) { queryCustomData = true; key = tokens[2]; } Device device = DeviceManager.getStoredDevice(context); if (queryCustomData) { CustomData customData = device.getCustomData(); if (customData != null) { return (Comparable) customData.opt(key); } } else { return (Comparable) device.opt(key); } } case app_release: { String key = tokens[1]; AppRelease appRelease = AppReleaseManager.getStoredAppRelease(context); return (Comparable) appRelease.opt(key); } case sdk: String key = tokens[1]; Sdk sdk = SdkManager.getStoredSdk(context); return (Comparable) sdk.opt(key); default: break; } return null; } @Override public boolean apply(Context context) { Log.v("Comparison Predicate: %s", query); Comparable value = getValue(context, query); Log.v(" => %s", value); for (Condition condition : conditions) { condition.operand = normalize(value, condition.operand); Log.v("-- Compare: %s %s %s", getLoggableValue(value), condition.operation, getLoggableValue(condition.operand)); switch (condition.operation) { case $gt: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (!(value.compareTo(operand) > 0)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s > %s", value, condition.operand)); } break; } case $gte: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (!(value.compareTo(operand) >= 0)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s >= %s", value, condition.operand)); } break; } case $eq: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (!value.equals(operand)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s == %s", value, condition.operand)); } break; } case $ne: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (value.equals(operand)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s != %s", value, condition.operand)); } break; } case $lte: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (!(value.compareTo(operand) <= 0)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s <= %s", value, condition.operand)); } break; } case $lt: { if (value == null) { return false; } if (condition.operand instanceof Comparable) { Comparable operand = (Comparable) condition.operand; if (!(value.compareTo(operand) < 0)) { return false; } } else { throw new IllegalArgumentException( String.format("Can't compare %s < %s", value, condition.operand)); } break; } case $exists: { if (!(condition.operand instanceof Boolean)) { throw new IllegalArgumentException( String.format("Argument %s is not a boolean", condition.operand)); } boolean shouldExist = (Boolean) condition.operand; boolean exists = value != null; return exists == shouldExist; } case $contains: { if (value == null) { return false; } boolean ret = false; if (value instanceof String && condition.operand instanceof String) { ret = ((String) value).toLowerCase().contains(((String) condition.operand).toLowerCase()); } return ret; } case $starts_with: { if (value == null) { return false; } boolean ret = false; if (value instanceof String && condition.operand instanceof String) { ret = ((String) value).toLowerCase().startsWith(((String) condition.operand).toLowerCase()); } return ret; } case $ends_with: { if (value == null) { return false; } boolean ret = false; if (value instanceof String && condition.operand instanceof String) { ret = ((String) value).toLowerCase().endsWith(((String) condition.operand).toLowerCase()); } return ret; } default: break; } } return true; } private String getLoggableValue(Object input) { if (input != null) { if (input instanceof String) { return "\"" + input + "\""; } else { return input.toString(); } } else { return "null"; } } private enum QueryType { application_version, application_build, current_time, is_update, time_since_install, code_point, interactions, person, device, app_release, sdk, other; public static QueryType parse(String name) { try { return QueryType.valueOf(name); } catch (IllegalArgumentException e) { // Ignore } return other; } } private enum ValueFilterType { invokes, other; public static ValueFilterType parse(String name) { try { return ValueFilterType.valueOf(name); } catch (IllegalArgumentException e) { // Ignore } return other; } } private enum ValueSubFilterType { total, version, build, time_ago, other; public static ValueSubFilterType parse(String name) { try { return ValueSubFilterType.valueOf(name); } catch (IllegalArgumentException e) { // Ignore } return other; } } }