Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(348)

Unified Diff: plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java

Issue 6312048: Redo normal expressions in dialog updater to be thread-safe (Closed) Base URL: https://chromedevtools.googlecode.com/svn/trunk
Patch Set: follow codereview Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/WizardUtils.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
diff --git a/plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java b/plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
index c3f7c2132042bc0ca78877f52b07790b027932eb..17d5d0c7de6c064ca8a1bde2943a26be90cbe13f 100644
--- a/plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
+++ b/plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/DialogUtils.java
@@ -4,6 +4,15 @@
package org.chromium.debug.ui;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -310,7 +319,7 @@ public class DialogUtils {
* Creates a switcher that is operated by optional expression.
* @param <T> type of expression
*/
- <T> OptionalSwitcher<T> addOptionalSwitch(Gettable<? extends Optional<T>> expression);
+ <T> OptionalSwitcher<T> addOptionalSwitch(Gettable<? extends Optional<? extends T>> expression);
/**
* Creates a switcher that is operated by non-optional expression.
@@ -358,8 +367,8 @@ public class DialogUtils {
* all sources have optional type and the merge source itself of optional type. The switcher
* expression may have error value, in this case the merger also returns this error value.
*/
- <P> ValueSource<? extends Optional<P>> createOptionalMerge(
- ValueSource<? extends Optional<P>> ... sources);
+ <P> ValueSource<? extends Optional<? extends P>> createOptionalMerge(
+ ValueSource<? extends Optional<? extends P>> ... sources);
}
public static <T> ValueSource<T> createConstant(final T constnant, Updater updater) {
@@ -403,6 +412,28 @@ public class DialogUtils {
public boolean isNormal() {
return true;
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!obj.getClass().equals(this.getClass())) {
+ return false;
+ }
+ Optional<?> other = (Optional<?>) obj;
+ if (value == null) {
+ return other.getNormal() == null;
+ } else {
+ return value.equals(other.getNormal());
+ }
+ }
+ @Override
+ public int hashCode() {
+ return value == null ? 0 : value.hashCode();
+ }
};
}
@@ -421,6 +452,28 @@ public class DialogUtils {
public boolean isNormal() {
return false;
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!obj.getClass().equals(this.getClass())) {
+ return false;
+ }
+ Optional<?> other = (Optional<?>) obj;
+ if (messages == null) {
+ return other.errorMessages() == null;
+ } else {
+ return messages.equals(other.errorMessages());
+ }
+ }
+ @Override
+ public int hashCode() {
+ return messages.hashCode();
+ }
};
}
@@ -502,70 +555,225 @@ public class DialogUtils {
}
/**
- * An interface similar to {@link Gettable}, but with a quite specific contract:
- * it may depend on some optional values, but its calculate method should only be called
- * when all of the values are normal (non-error). This way its implementations becomes simpler.
- * It's up to someone who calls calculate method to check that contract is held.
+ * An expression that gets calculated only when its dependencies are all non-error.
+ * The interface contains a "calculate" method and several methods that return expression
+ * dependencies.
+ * <p>
+ * The one that is using this expression is responsible for reading all sources, checking
+ * whether the optional values are normal and calling "calculate" method passing the
+ * normal values as arguments.
+ * <p>
+ * The interface is reflection-oriented, as you cannot express type schema in plain Java.
+ * The client should check the interface for a type-consistency statically on runtime and
+ * throw exception if something is wrong. All user types are explicitly declared in
+ * signature of methods. This allows accurate type checking via reflection (including
+ * generics parameters, which are otherwise erased on runtime).
+ * <p>
+ * The interface is reflection-based. It only contains annotation that method should have.
+ *
+ * @param <T> type of value this expression returns
*/
- public interface NormalExpression<RES> {
- RES calculate();
- }
+ public interface NormalExpression<T> {
+ /**
+ * An annotation for a "calculate" method of the interface. There should be only one such
+ * a method in the object. Its return type should be "T" or "Optional<? extends T>" (we are
+ * flexible in this only place). It should have arguments one per its dependency
+ * (in the same order).
+ * <p>The method must be declared public for the reflection to work.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @interface Calculate {
+ }
- /**
- * Creates a {@link ValueProcessor} that is backed by {@link NormalExpression}.
- * @param optionalSources list of inputs that are optional and thus have to be checked for
- * {@link NormalExpression} contract.
- */
- public static <T> ValueProcessor<Optional<T>> createOptionalProcessor(
- final NormalExpression<T> expression,
- ValueSource<? extends Optional<?>> ... optionalSources) {
- final Gettable<Optional<T>> getter = handleErrors(expression, optionalSources);
- return createProcessor(getter);
+ /**
+ * An annotation for a method that returns expression dependency. It should have no arguments
+ * and return IValueSource<? extends Optional<*T*>> type ("IValueSource" is significant here).
+ * The type *T* should correspond to the type of "calculate" method argument (dependency
+ * methods should go in the same order as "calculate" arguments go).
+ * <p>The method must be declared public for the reflection to work.
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface DependencyGetter {
+ }
}
+
/**
- * Implements the basic contract of {@link NormalExpression}. Wraps it as {@link Gettable} and
- * keeps all its optional sources that are checked before each calculation.
+ * Converts {@link NormalExpression} into {@link Gettable} and takes responsibility of checking
+ * that all dependencies have only normal values. Despite {@link NormalExpression} being
+ * reflection-based interface, this method should be completely type-safe for a programmer and
+ * accurately check (statically) that its signatures are consistent (including generic types).
*/
- public static <RES> Gettable<Optional<RES>> handleErrors(final NormalExpression<RES> expression,
- final ValueSource<? extends Optional<?>> ... optionalSources) {
- NormalExpression<Optional<RES>> wrapper = new NormalExpression<Optional<RES>>() {
- public Optional<RES> calculate() {
- return createOptional(expression.calculate());
+ public static <RES> Gettable<Optional<? extends RES>> handleErrors(
+ final NormalExpression<RES> expression) {
+ Class<?> expressionClass = expression.getClass();
+
+ // All reflection is done in generic-aware API generation.
+
+ // Read generic NormalExpression type parameter of expression class.
+ Type expressionType;
+ {
+ ParameterizedType normalExpressionType = null;
+ for (Type inter : expressionClass.getGenericInterfaces()) {
+ if (inter instanceof ParameterizedType == false) {
+ continue;
+ }
+ ParameterizedType parameterizedType = (ParameterizedType) inter;
+ if (!parameterizedType.getRawType().equals(NormalExpression.class)) {
+ continue;
+ }
+ normalExpressionType = parameterizedType;
}
- };
- return handleErrorsAddNew(wrapper, optionalSources);
- }
+ if (normalExpressionType == null) {
+ throw new IllegalArgumentException("Expression does not directly implement " +
+ NormalExpression.class.getName());
+ }
+ expressionType = normalExpressionType.getActualTypeArguments()[0];
+ }
- /**
- * Implements the basic contract of {@link NormalExpression}. Wraps it as {@link Gettable} and
- * keeps all its optional sources that are checked before each calculation.
- * The expression may rely on all optionalSources being of normal values, but it is allowed to
- * return error value itself.
- */
- public static <RES> Gettable<Optional<RES>> handleErrorsAddNew(
- final NormalExpression<Optional<RES>> expression,
- final ValueSource<? extends Optional<?>> ... optionalSources) {
- return new Gettable<Optional<RES>>() {
- public Optional<RES> getValue() {
- boolean hasErrors = false;
- for (ValueSource<? extends Optional<?>> source : optionalSources) {
- if (!source.getValue().isNormal()) {
- hasErrors = true;
- break;
+ // Read all methods of expression class and choose annotated ones.
+ Method calculateMethodVar = null;
+ final List<Method> dependencyMethods = new ArrayList<Method>(2);
+ for (Method m : expressionClass.getMethods()) {
+ if (m.getAnnotation(NormalExpression.Calculate.class) != null) {
+ if (calculateMethodVar != null) {
+ throw new IllegalArgumentException("Class " + expressionClass.getName() +
+ " has more than one method with " +
+ NormalExpression.Calculate.class.getName() + " annotation");
+ }
+ calculateMethodVar = m;
+ }
+ if (m.getAnnotation(NormalExpression.DependencyGetter.class) != null) {
+ dependencyMethods.add(m);
+ }
+ }
+ if (calculateMethodVar == null) {
+ throw new IllegalArgumentException("Failed to found Class method with " +
+ NormalExpression.Calculate.class.getName() + " annotation in " +
+ expressionClass.getName());
+ }
+ final Method calculateMethod = calculateMethodVar;
+ Type methodReturnType = calculateMethod.getGenericReturnType();
+
+ // Method is typically in anonymous class. Making it accessible is required.
+ calculateMethod.setAccessible(true);
+
+ // Prepare handling method return value (it's either a plain value or an optional wrapper).
+ abstract class ReturnValueHandler {
+ abstract Optional<? extends RES> castResult(Object resultObject);
+ }
+
+ final ReturnValueHandler returnValueHandler;
+
+ if (methodReturnType.equals(expressionType)) {
+ returnValueHandler = new ReturnValueHandler() {
+ Optional<? extends RES> castResult(Object resultObject) {
+ // Return type in interface is RES.
+ // Type cast has been proven to be correct.
+ return createOptional((RES) resultObject);
+ }
+ };
+ } else {
+ tryUnwrapOptional: {
+ if (methodReturnType instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) methodReturnType;
+ if (parameterizedType.getRawType() == Optional.class) {
+ Type optionalParam = parameterizedType.getActualTypeArguments()[0];
+ boolean okToCast = false;
+ if (optionalParam instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) optionalParam;
+ if (wildcardType.getUpperBounds()[0].equals(expressionType)) {
+ okToCast = true;
+ }
+ } else if (optionalParam.equals(expressionType)) {
+ okToCast = true;
+ }
+ if (okToCast) {
+ returnValueHandler = new ReturnValueHandler() {
+ Optional<? extends RES> castResult(Object resultObject) {
+ // Return type in interface is optional wrapper around RES.
+ // Type cast has been proven to be correct.
+ return (Optional<? extends RES>) resultObject;
+ }
+ };
+ break tryUnwrapOptional;
+ }
}
}
+ throw new IllegalArgumentException("Wrong return type " + methodReturnType +
+ ", expected: " + expressionType);
+ }
+ }
+
+ // Check that dependencies correspond to "calculate" method arguments.
+ Type[] methodParamTypes = calculateMethod.getGenericParameterTypes();
+ if (methodParamTypes.length != dependencyMethods.size()) {
+ throw new IllegalArgumentException("Wrong number of agruments in calculate method " +
+ calculateMethod);
+ }
+ // We depend on methods being ordered in Java reflection.
+ for (int i = 0; i < methodParamTypes.length; i++) {
+ Method depMethod = dependencyMethods.get(i);
+ try {
+ if (depMethod.getParameterTypes().length != 0) {
+ throw new IllegalArgumentException("Dependency method should not have arguments");
+ }
+ Type depType = depMethod.getGenericReturnType();
+ if (depType instanceof ParameterizedType == false) {
+ throw new IllegalArgumentException("Dependency has wrong return type: " + depType);
+ }
+ ParameterizedType depParameterizedType = (ParameterizedType) depType;
+ if (depParameterizedType.getRawType() != ValueSource.class) {
+ throw new IllegalArgumentException("Dependency has wrong return type: " + depType);
+ }
+ // Method is typically in anonymous class. Making it accessible is required.
+ depMethod.setAccessible(true);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Failed to process method " + depMethod, e);
+ }
+ }
- if (hasErrors) {
- Set<Message> errors = new LinkedHashSet<Message>(0);
- for (ValueSource<? extends Optional<?>> source : optionalSources) {
- if (!source.getValue().isNormal()) {
- errors.addAll(source.getValue().errorMessages());
+ // Create implementation that will call methods via reflection.
+ return new Gettable<Optional<? extends RES>>() {
+ @Override
+ public Optional<? extends RES> getValue() {
+ Object[] params = new Object[dependencyMethods.size()];
+ Set<Message> errors = null;
+ for (int i = 0; i < params.length; i++) {
+ Object sourceObject;
+ try {
+ sourceObject = dependencyMethods.get(i).invoke(expression);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ ValueSource<? extends Optional<?>> source =
+ (ValueSource<? extends Optional<?>>) sourceObject;
+ Optional<?> optionalValue = source.getValue();
+ if (optionalValue.isNormal()) {
+ params[i] = optionalValue.getNormal();
+ } else {
+ if (errors == null) {
+ errors = new LinkedHashSet<Message>(0);
}
+ errors.addAll(optionalValue.errorMessages());
}
- return createErrorOptional(errors);
+ }
+ if (errors == null) {
+ Object result;
+ try {
+ result = calculateMethod.invoke(expression, params);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ return returnValueHandler.castResult(result);
} else {
- return expression.calculate();
+ return createErrorOptional(errors);
}
}
};
@@ -871,21 +1079,23 @@ public class DialogUtils {
setCurrentScope(newScope);
}
- public <P> ValueSource<? extends Optional<P>> createOptionalMerge(
- ValueSource<? extends Optional<P>>... sources) {
+ public <P> ValueSource<? extends Optional<? extends P>> createOptionalMerge(
+ ValueSource<? extends Optional<? extends P>>... sources) {
- final Map<T, ValueSource<? extends Optional<P>>> map = sortSources(Arrays.asList(sources));
+ final Map<T, ValueSource<? extends Optional<? extends P>>> map =
+ sortSources(Arrays.asList(sources));
- ValueProcessor<? extends Optional<P>> result = new ValueProcessor<Optional<P>>() {
+ ValueProcessor<? extends Optional<? extends P>> result =
+ new ValueProcessor<Optional<? extends P>>() {
public void update(Updater updater) {
setCurrentValue(calculate());
updater.reportChanged(this);
}
- private Optional<P> calculate() {
+ private Optional<? extends P> calculate() {
Optional<? extends T> control = sourceForMerge.getValue();
if (control.isNormal()) {
- ValueSource<? extends Optional<P>> oneSource = map.get(control.getNormal());
+ ValueSource<? extends Optional<? extends P>> oneSource = map.get(control.getNormal());
return oneSource.getValue();
} else {
return createErrorOptional(control.errorMessages());
@@ -952,7 +1162,8 @@ public class DialogUtils {
this.updater = updater;
}
- public <P> OptionalSwitcher<P> addOptionalSwitch(Gettable<? extends Optional<P>> expression) {
+ public <P> OptionalSwitcher<P> addOptionalSwitch(
+ Gettable<? extends Optional<? extends P>> expression) {
OptionalSwitcherImpl<P> switcher = new OptionalSwitcherImpl<P>(this, expression);
updater.addConsumer(this, switcher.getValueConsumer());
updater.addSource(this, switcher.getSourceForMerge());
« no previous file with comments | « no previous file | plugins/org.chromium.debug.ui/src/org/chromium/debug/ui/WizardUtils.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698