From 1b2fb3e10f1863ac64a471718bc072a4792eda7e Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Fri, 17 Jan 2014 10:33:23 -0500 Subject: [PATCH] JCLOUDS-427: Avoiding Guava reflection code broken in Java 7u51 Backport of 8ff60020f - Adding a version of TypeToken.where that replaces two parameters in one go - Avoiding TypeToken type parameter replacement with wildcard bounds Works around https://code.google.com/p/guava-libraries/issues/detail?id=1635 All reflection magic now :-( --- .../org/jclouds/rest/config/BinderUtils.java | 17 +-- .../rest/internal/BaseRestApiMetadata.java | 9 +- .../java/org/jclouds/util/TypeToken2.java | 104 ++++++++++++++++++ 3 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/jclouds/util/TypeToken2.java diff --git a/core/src/main/java/org/jclouds/rest/config/BinderUtils.java b/core/src/main/java/org/jclouds/rest/config/BinderUtils.java index 5f201cc175..c8d3963b7a 100644 --- a/core/src/main/java/org/jclouds/rest/config/BinderUtils.java +++ b/core/src/main/java/org/jclouds/rest/config/BinderUtils.java @@ -22,6 +22,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Binder; import com.google.inject.TypeLiteral; +import org.jclouds.util.TypeToken2; +import org.jclouds.util.TypeToken2.TypeParameter2; + /** * * @author Adrian Cole @@ -100,13 +103,13 @@ public class BinderUtils { @Deprecated @SuppressWarnings({ "unchecked", "serial" }) private static void bindHttpApiProvider(Binder binder, Class sync, Class async) { - TypeToken> token = new TypeToken>() { - }.where(new TypeParameter() { - }, sync).where(new TypeParameter() { + TypeToken> token = new TypeToken2>() { + }.where(new TypeParameter2() { + }, sync, new TypeParameter2() { }, async); binder.bind(sync).toProvider(TypeLiteral.class.cast(TypeLiteral.get(token.getType()))); } - + /** * adds an explicit binding for an interface which synchronously blocks on * similar calls to an {@code async} type. @@ -140,9 +143,9 @@ public class BinderUtils { @Deprecated @SuppressWarnings({ "unchecked", "serial" }) private static void bindCallGetOnFutures(Binder binder, Class sync, Class async) { - TypeToken> token = new TypeToken>() { - }.where(new TypeParameter() { - }, sync).where(new TypeParameter() { + TypeToken> token = new TypeToken2>() { + }.where(new TypeParameter2() { + }, sync, new TypeParameter2() { }, async); binder.bind(sync).toProvider(TypeLiteral.class.cast(TypeLiteral.get(token.getType()))); } diff --git a/core/src/main/java/org/jclouds/rest/internal/BaseRestApiMetadata.java b/core/src/main/java/org/jclouds/rest/internal/BaseRestApiMetadata.java index 6b9d30339d..23f802dbb9 100644 --- a/core/src/main/java/org/jclouds/rest/internal/BaseRestApiMetadata.java +++ b/core/src/main/java/org/jclouds/rest/internal/BaseRestApiMetadata.java @@ -24,10 +24,11 @@ import java.util.Properties; import org.jclouds.apis.ApiMetadata; import org.jclouds.apis.internal.BaseApiMetadata; import org.jclouds.rest.RestApiMetadata; +import org.jclouds.util.TypeToken2; +import org.jclouds.util.TypeToken2.TypeParameter2; import com.google.common.annotations.Beta; import com.google.common.base.Objects.ToStringHelper; -import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; /** @@ -55,10 +56,10 @@ public abstract class BaseRestApiMetadata extends BaseApiMetadata implements Res } public static TypeToken> contextToken(TypeToken apiToken, TypeToken asyncApiToken) { - return new TypeToken>() { + return new TypeToken2>() { private static final long serialVersionUID = 1L; - }.where(new TypeParameter() { - }, apiToken).where(new TypeParameter() { + }.where(new TypeParameter2() { + }, apiToken, new TypeParameter2() { }, asyncApiToken); } diff --git a/core/src/main/java/org/jclouds/util/TypeToken2.java b/core/src/main/java/org/jclouds/util/TypeToken2.java new file mode 100644 index 0000000000..1fc0ae3570 --- /dev/null +++ b/core/src/main/java/org/jclouds/util/TypeToken2.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Map; + +import org.jclouds.reflect.Reflection2; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.Invokable; +import com.google.common.reflect.TypeParameter; +import com.google.common.reflect.TypeToken; + +/* + * FIXME: remove this class ASAP! + * + * Evil stuff, adapted from https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/reflect/TypeToken.java#236. + * See https://issues.apache.org/jira/browse/JCLOUDS-427 and + * https://code.google.com/p/guava-libraries/issues/detail?id=1635 + */ +public class TypeToken2 extends TypeToken { + private static final long serialVersionUID = 1L; + + @SuppressWarnings("unchecked") + public TypeToken where(TypeParameter2 typeParam1, + TypeToken typeArg1, TypeParameter2 typeParam2, TypeToken typeArg2) { + /* + * Resolving both parameters in one shot seems to work around 1635, but + * TypeResolver and where(Map) are package-private in Guava 14.x + */ + Type resolvedType; + try { + Object resolver = newTypeResolver(); + Invokable whereWithMap = Reflection2.method( + (Class) resolver.getClass(), "where", Map.class); + resolver = whereWithMap.invoke(resolver, ImmutableMap.of( + typeParam1.getTypeVariable(), typeArg1.getType(), + typeParam2.getTypeVariable(), typeArg2.getType())); + Invokable resolveType = Reflection2.method( + (Class) resolver.getClass(), "resolveType", Type.class); + resolvedType = resolveType.invoke(resolver, getType()); + } catch (IllegalAccessException exception) { + // should never happen + throw new IllegalStateException(exception); + } catch (InvocationTargetException exception) { + // should never happen + throw new IllegalStateException(exception); + } catch (SecurityException exception) { + // should never happen + throw new IllegalStateException(exception); + } catch (IllegalArgumentException exception) { + // should never happen + throw new IllegalStateException(exception); + } catch (NoSuchFieldException exception) { + // should never happen + throw new IllegalStateException(exception); + } catch (NoSuchMethodException exception) { + // should never happen + throw new IllegalStateException(exception); + } + return (TypeToken) TypeToken.of(resolvedType); + } + + private static Object newTypeResolver() throws SecurityException, NoSuchFieldException, + IllegalArgumentException, IllegalAccessException, InvocationTargetException, + NoSuchMethodException { + Field typeResolverField = TypeToken.class.getDeclaredField("typeResolver"); + Invokable typeResolverContrucutor = + Invokable.from(typeResolverField.getType().getConstructor()); + return typeResolverContrucutor.invoke(null); + } + + public TypeToken where(TypeParameter2 typeParam1, Class typeArg1, + TypeParameter2 typeParam2, Class typeArg2) { + return where(typeParam1, of(typeArg1), typeParam2, of(typeArg2)); + } + + public abstract static class TypeParameter2 extends TypeParameter { + TypeVariable getTypeVariable() { + // duplicated from TypeCapture, where it's package-private + Type superclass = getClass().getGenericSuperclass(); + return (TypeVariable) ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + } +}