package com.mixpanel.android.mpmetrics;
import android.support.annotation.IntDef;
import com.mixpanel.android.util.MPLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* In general, you won't need to interact with this class directly -
* it's used internally to communicate changes in values to the tweaks you define with
* {@link MixpanelAPI#stringTweak(String, String)}, {@link MixpanelAPI#booleanTweak(String, boolean)},
* {@link MixpanelAPI#doubleTweak(String, double)}, {@link MixpanelAPI#longTweak(String, long)},
* and other tweak-related interfaces on MixpanelAPI.
*
* Instances of tweaks aren't available to library user code.
*/
public class Tweaks {
/**
* This method is used internally to expose tweaks to the Mixpanel UI,
* and will likely not be directly useful to code that imports the Mixpanel library.
* The given listener's onTweakDeclared method will be called when a new tweak is declared.
*/
public synchronized void addOnTweakDeclaredListener(OnTweakDeclaredListener listener) {
if (null == listener) {
throw new NullPointerException("listener cannot be null");
}
mTweakDeclaredListeners.add(listener);
}
/**
* Manually set the value of a tweak. Most users of the library will not need to call this
* directly - instead, the library will call set when new values of the tweak are published.
*/
public synchronized void set(String tweakName, Object value) {
if (!mTweakValues.containsKey(tweakName)) {
MPLog.w(LOGTAG, "Attempt to set a tweak \"" + tweakName + "\" which has never been defined.");
return;
}
final TweakValue container = mTweakValues.get(tweakName);
final TweakValue updated = container.updateValue(value);
mTweakValues.put(tweakName, updated);
}
public synchronized boolean isNewValue(String tweakName, Object value) {
if (!mTweakValues.containsKey(tweakName)) {
MPLog.w(LOGTAG, "Attempt to reference a tweak \"" + tweakName + "\" which has never been defined.");
return false;
}
final TweakValue container = mTweakValues.get(tweakName);
return !container.value.equals(value);
}
/**
* Returns the descriptions of all tweaks that currently exist.
*
* The Mixpanel library uses this method internally to expose tweaks and their types to the UI. Most
* users will not need to call this method directly.
*/
public synchronized Map<String, TweakValue> getAllValues() {
return new HashMap<String, TweakValue>(mTweakValues);
}
public synchronized Map<String, TweakValue> getDefaultValues() {
return new HashMap<String, TweakValue>(mTweakDefaultValues);
}
@IntDef({
BOOLEAN_TYPE,
DOUBLE_TYPE,
LONG_TYPE,
STRING_TYPE
})
@Retention(RetentionPolicy.SOURCE)
private @interface TweakType {}
/**
* An internal description of the type of a tweak.
* These values are used internally to expose
* tweaks to the Mixpanel UI, and will likely not be directly useful to
* code that imports the Mixpanel library.
*/
public static final @TweakType int BOOLEAN_TYPE = 1;
/**
* An internal description of the type of a tweak.
* These values are used internally to expose
* tweaks to the Mixpanel UI, and will likely not be directly useful to
* code that imports the Mixpanel library.
*/
public static final @TweakType int DOUBLE_TYPE = 2;
/**
* An internal description of the type of a tweak.
* These values are used internally to expose
* tweaks to the Mixpanel UI, and will likely not be directly useful to
* code that imports the Mixpanel library.
*/
public static final @TweakType int LONG_TYPE = 3;
/**
* An internal description of the type of a tweak.
* These values are used internally to expose
* tweaks to the Mixpanel UI, and will likely not be directly useful to
* code that imports the Mixpanel library.
*/
public static final @TweakType int STRING_TYPE = 4;
/**
* Represents the value and definition of a tweak known to the system. This class
* is used internally to expose tweaks to the Mixpanel UI,
* and will likely not be directly useful to code that imports the Mixpanel library.
*/
public static class TweakValue {
private TweakValue(@TweakType int aType, Object aDefaultValue, Number aMin, Number aMax, Object value) {
type = aType;
defaultValue = aDefaultValue;
minimum = aMin;
maximum = aMax;
this.value = value;
}
public TweakValue updateValue(Object newValue) {
return new TweakValue(type, defaultValue, minimum, maximum, newValue);
}
public String getStringValue() {
String ret = null;
try {
ret = (String) defaultValue;
} catch (ClassCastException e) {
; // ok
}
try {
ret = (String) value;
} catch (ClassCastException e) {
; // ok
}
return ret;
}
public Number getNumberValue() {
Number ret = 0;
if (null != defaultValue) {
try {
ret = (Number) defaultValue;
} catch (ClassCastException e){
; // ok
}
}
if (null != value) {
try {
ret = (Number) value;
} catch (ClassCastException e) {
; // ok
}
}
return ret;
}
public Boolean getBooleanValue() {
Boolean ret = false;
if (null != defaultValue) {
try {
ret = (Boolean) defaultValue;
} catch (ClassCastException e) {
; // ok
}
}
if (null != value) {
try {
ret = (Boolean) value;
} catch (ClassCastException e) {
; // ok
}
}
return ret;
}
public final @TweakType int type;
protected final Object value;
private final Object defaultValue;
private final Number minimum;
private final Number maximum;
}
/**
* This interface is used internally to expose tweaks to the Mixpanel UI,
* and will likely not be directly useful to code that imports the Mixpanel library.
*/
public interface OnTweakDeclaredListener {
void onTweakDeclared();
}
/* package */ Tweaks() {
mTweakValues = new ConcurrentHashMap<>();
mTweakDefaultValues = new ConcurrentHashMap<>();
mTweakDeclaredListeners = new ArrayList<>();
}
/* package */ Tweak<String> stringTweak(final String tweakName, final String defaultValue) {
declareTweak(tweakName, defaultValue, STRING_TYPE);
return new Tweak<String>() {
@Override
public String get() {
final TweakValue tweakValue = getValue(tweakName);
return tweakValue.getStringValue();
}
};
}
/* package */ Tweak<Double> doubleTweak(final String tweakName, final double defaultValue) {
declareTweak(tweakName, defaultValue, DOUBLE_TYPE);
return new Tweak<Double>() {
@Override
public Double get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.doubleValue();
}
};
}
/* package */ Tweak<Float> floatTweak(final String tweakName, final float defaultValue) {
declareTweak(tweakName, defaultValue, DOUBLE_TYPE);
return new Tweak<Float>() {
@Override
public Float get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.floatValue();
}
};
}
/* package */ Tweak<Long> longTweak(final String tweakName, final long defaultValue) {
declareTweak(tweakName, defaultValue, LONG_TYPE);
return new Tweak<Long>() {
@Override
public Long get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.longValue();
}
};
}
/* package */ Tweak<Integer> intTweak(final String tweakName, final int defaultValue) {
declareTweak(tweakName, defaultValue, LONG_TYPE);
return new Tweak<Integer>() {
@Override
public Integer get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.intValue();
}
};
}
/* package */ Tweak<Byte> byteTweak(final String tweakName, final byte defaultValue) {
declareTweak(tweakName, defaultValue, LONG_TYPE);
return new Tweak<Byte>() {
@Override
public Byte get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.byteValue();
}
};
}
/* package */ Tweak<Short> shortTweak(final String tweakName, final short defaultValue) {
declareTweak(tweakName, defaultValue, LONG_TYPE);
return new Tweak<Short>() {
@Override
public Short get() {
final TweakValue tweakValue = getValue(tweakName);
final Number result = tweakValue.getNumberValue();
return result.shortValue();
}
};
}
/* package */ Tweak<Boolean> booleanTweak(final String tweakName, final boolean defaultValue) {
declareTweak(tweakName, defaultValue, BOOLEAN_TYPE);
return new Tweak<Boolean>() {
@Override
public Boolean get() {
final TweakValue tweakValue = getValue(tweakName);
return tweakValue.getBooleanValue();
}
};
}
private synchronized TweakValue getValue(String tweakName) {
return mTweakValues.get(tweakName);
}
private void declareTweak(String tweakName, Object defaultValue, @TweakType int tweakType) {
if (mTweakValues.containsKey(tweakName)) {
MPLog.w(LOGTAG, "Attempt to define a tweak \"" + tweakName + "\" twice with the same name");
return;
}
final TweakValue value = new TweakValue(tweakType, defaultValue, null, null, defaultValue);
mTweakValues.put(tweakName, value);
mTweakDefaultValues.put(tweakName, value);
final int listenerSize = mTweakDeclaredListeners.size();
for (int i = 0; i < listenerSize; i++) {
mTweakDeclaredListeners.get(i).onTweakDeclared();
}
}
// All access to mTweakValues must be synchronized
private final ConcurrentMap<String, TweakValue> mTweakValues;
private final ConcurrentMap<String, TweakValue> mTweakDefaultValues;
private final List<OnTweakDeclaredListener> mTweakDeclaredListeners;
private static final String LOGTAG = "MixpanelAPI.Tweaks";
}