/*
* Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Trident Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.trident;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.pushingpixels.trident.interpolator.KeyFrames;
import org.pushingpixels.trident.interpolator.PropertyInterpolator;
public class TimelinePropertyBuilder<T> {
/**
* Defines how to set a property.
*/
public static interface PropertySetter<T> {
public void set(Object obj, String fieldName, T value);
}
/**
* Defines how to get a property.
*/
public static interface PropertyGetter<T> {
public T get(Object obj, String fieldName);
}
/**
* Defines how to access a property.
*/
public static interface PropertyAccessor<T> extends PropertyGetter<T>,
PropertySetter<T> {
}
/**
* Default property setter.
*/
public static class DefaultPropertySetter<T> implements PropertySetter<T> {
private Method setterMethod;
public DefaultPropertySetter(Object obj, String fieldName) {
setterMethod = getSetter(obj, fieldName);
}
public void set(Object obj, String fieldName, T value) {
try {
setterMethod.invoke(obj, value);
} catch (Throwable t) {
throw new RuntimeException(
"Unable to set the value of the field '" + fieldName
+ "'", t);
}
}
}
/**
* Default property getter.
*/
public static class DefaultPropertyGetter<T> implements PropertyGetter<T> {
private Method getterMethod;
public DefaultPropertyGetter(Object obj, String fieldName) {
getterMethod = getGetter(obj, fieldName);
}
public T get(Object obj, String fieldName) {
try {
return (T) getterMethod.invoke(obj);
} catch (Throwable t) {
throw new RuntimeException(
"Unable to get the value of the field '" + fieldName
+ "'", t);
}
}
}
private Object target; // may be null
private final String propertyName; // required
private T from; // optional
private boolean isFromCurrent;
private T to; // must be optional because of KeyFrames
private PropertyInterpolator<T> interpolator; // optional
private PropertyGetter<T> getter; // optional
private PropertySetter<T> setter; // optional
private KeyFrames<T> keyFrames; // optional
TimelinePropertyBuilder(String propertyName) {
this.propertyName = propertyName;
this.isFromCurrent = false;
}
public TimelinePropertyBuilder<T> from(T startValue) {
if (this.from != null) {
throw new IllegalArgumentException("from() can only be called once");
}
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"from() cannot be called after fromCurrent()");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"from() cannot be called after goingThrough()");
}
this.from = startValue;
return this;
}
public TimelinePropertyBuilder<T> fromCurrent() {
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"fromCurrent() can only be called once");
}
if (this.from != null) {
throw new IllegalArgumentException(
"fromCurrent() cannot be called after from()");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"fromCurrent() cannot be called after goingThrough()");
}
this.isFromCurrent = true;
return this;
}
public TimelinePropertyBuilder<T> to(T endValue) {
if (this.to != null) {
throw new IllegalArgumentException("to() can only be called once");
}
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"to() cannot be called after goingThrough()");
}
this.to = endValue;
return this;
}
public TimelinePropertyBuilder<T> on(Object object) {
this.target = object;
return this;
}
public TimelinePropertyBuilder<T> interpolatedWith(
PropertyInterpolator<T> pInterpolator) {
if (this.interpolator != null) {
throw new IllegalArgumentException(
"interpolateWith() can only be called once");
}
this.interpolator = pInterpolator;
return this;
}
public TimelinePropertyBuilder<T> setWith(PropertySetter<T> pSetter) {
if (this.setter != null) {
throw new IllegalArgumentException(
"setWith() can only be called once");
}
this.setter = pSetter;
return this;
}
public TimelinePropertyBuilder<T> getWith(PropertyGetter<T> pGetter) {
if (this.getter != null) {
throw new IllegalArgumentException(
"getWith() can only be called once");
}
this.getter = pGetter;
return this;
}
public TimelinePropertyBuilder<T> accessWith(PropertyAccessor<T> pAccessor) {
if (this.setter != null || this.getter != null) {
throw new IllegalArgumentException(
"accessWith() can only be called once");
}
this.setter = pAccessor;
this.getter = pAccessor;
return this;
}
public TimelinePropertyBuilder<T> goingThrough(KeyFrames<T> keyFrames) {
if (this.keyFrames != null) {
throw new IllegalArgumentException(
"goingThrough() can only be called once");
}
if (this.isFromCurrent) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after fromCurrent()");
}
if (this.from != null) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after from()");
}
if (this.to != null) {
throw new IllegalArgumentException(
"goingThrough() cannot be called after to()");
}
this.keyFrames = keyFrames;
return this;
}
AbstractFieldInfo getFieldInfo(Timeline timeline) {
if (this.target == null) {
this.target = timeline.mainObject;
}
if (this.keyFrames != null) {
return new KeyFramesFieldInfo(this.target, this.propertyName,
this.keyFrames, this.setter);
}
if (this.isFromCurrent) {
if (this.interpolator == null) {
this.interpolator = TridentConfig.getInstance()
.getPropertyInterpolator(this.to);
if (this.interpolator == null) {
throw new IllegalArgumentException(
"No interpolator found for "
+ this.to.getClass().getName());
}
}
return new GenericFieldInfoTo(this.target, this.propertyName,
this.to, this.interpolator, this.getter, this.setter);
}
if (this.interpolator == null) {
this.interpolator = TridentConfig.getInstance()
.getPropertyInterpolator(this.from, this.to);
if (this.interpolator == null) {
throw new IllegalArgumentException("No interpolator found for "
+ this.from.getClass().getName() + ":"
+ this.to.getClass().getName());
}
}
return new GenericFieldInfo(this.target, this.propertyName, this.from,
this.to, this.interpolator, this.setter);
}
abstract class AbstractFieldInfo<T> {
protected Object object;
protected String fieldName;
protected PropertyGetter getter;
protected PropertySetter setter;
protected T from;
protected T to;
AbstractFieldInfo(Object obj, String fieldName,
PropertyGetter<T> pGetter, PropertySetter<T> pSetter) {
this.object = obj;
this.fieldName = fieldName;
this.getter = pGetter;
this.setter = pSetter;
}
void setValues(T from, T to) {
this.from = from;
this.to = to;
}
abstract void onStart();
abstract void updateFieldValue(float timelinePosition);
}
private static <T> PropertyGetter<T> getPropertyGetter(Object obj,
String fieldName, PropertyGetter<T> pGetter) {
if (pGetter != null)
return pGetter;
return new DefaultPropertyGetter(obj, fieldName);
}
private static <T> PropertySetter<T> getPropertySetter(Object obj,
String fieldName, PropertySetter<T> pSetter) {
if (pSetter != null)
return pSetter;
return new DefaultPropertySetter(obj, fieldName);
}
private class GenericFieldInfoTo extends AbstractFieldInfo<Object> {
private PropertyInterpolator propertyInterpolator;
private Object to;
GenericFieldInfoTo(Object obj, String fieldName, Object to,
PropertyInterpolator propertyInterpolator,
PropertyGetter propertyGetter, PropertySetter propertySetter) {
super(obj, fieldName, getPropertyGetter(obj, fieldName,
propertyGetter), getPropertySetter(obj, fieldName,
propertySetter));
this.propertyInterpolator = propertyInterpolator;
this.to = to;
}
@Override
void onStart() {
this.from = getter.get(object, fieldName);
}
@Override
void updateFieldValue(float timelinePosition) {
try {
Object value = this.propertyInterpolator.interpolate(from, to,
timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
System.err.println("Exception occurred in updating field '"
+ this.fieldName + "' of object "
+ this.object.getClass().getCanonicalName()
+ " at timeline position " + timelinePosition);
exc.printStackTrace();
}
}
}
private class GenericFieldInfo extends AbstractFieldInfo<Object> {
private PropertyInterpolator propertyInterpolator;
GenericFieldInfo(Object obj, String fieldName, Object from, Object to,
PropertyInterpolator propertyInterpolator,
PropertySetter propertySetter) {
super(obj, fieldName, null, getPropertySetter(obj, fieldName,
propertySetter));
this.propertyInterpolator = propertyInterpolator;
this.setValues(from, to);
}
@Override
void onStart() {
}
@Override
void updateFieldValue(float timelinePosition) {
try {
Object value = this.propertyInterpolator.interpolate(from, to,
timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
System.err.println("Exception occurred in updating field '"
+ this.fieldName + "' of object "
+ this.object.getClass().getCanonicalName()
+ " at timeline position " + timelinePosition);
exc.printStackTrace();
}
}
}
private class KeyFramesFieldInfo extends AbstractFieldInfo<Object> {
KeyFrames keyFrames;
KeyFramesFieldInfo(Object obj, String fieldName, KeyFrames keyFrames,
PropertySetter propertySetter) {
super(obj, fieldName, null, getPropertySetter(obj, fieldName,
propertySetter));
this.keyFrames = keyFrames;
}
@Override
void onStart() {
}
@Override
void updateFieldValue(float timelinePosition) {
if (this.setter != null) {
try {
Object value = this.keyFrames.getValue(timelinePosition);
this.setter.set(this.object, this.fieldName, value);
} catch (Throwable exc) {
exc.printStackTrace();
}
}
}
}
private static Method getSetter(Object object, String propertyName) {
String setterMethodName = "set"
+ Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1);
Class oClazz = object.getClass();
while (oClazz != null) {
for (Method m : oClazz.getMethods()) {
if (setterMethodName.equals(m.getName())
&& (m.getParameterTypes().length == 1)
&& (m.getReturnType() == Void.TYPE)
&& (!Modifier.isStatic(m.getModifiers()))) {
return m;
}
}
oClazz = oClazz.getSuperclass();
}
return null;
}
private static Method getGetter(Object object, String propertyName) {
String getterMethodName = "get"
+ Character.toUpperCase(propertyName.charAt(0))
+ propertyName.substring(1);
Class oClazz = object.getClass();
while (oClazz != null) {
for (Method m : oClazz.getMethods()) {
if (getterMethodName.equals(m.getName())
&& (m.getParameterTypes().length == 0)
&& (!Modifier.isStatic(m.getModifiers()))) {
return m;
}
}
oClazz = oClazz.getSuperclass();
}
return null;
}
}