com.datastax.driver.core.GuavaCompatibility Maven / Gradle / Ivy
/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* Licensed 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 com.datastax.driver.core;
import com.datastax.driver.core.exceptions.DriverInternalError;
import com.google.common.collect.BiMap;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* A compatibility layer to support a wide range of Guava versions.
*
* The driver is compatible with Guava 16.0.1 or higher, but Guava 20 introduced incompatible breaking changes in its
* API, that could in turn be breaking for legacy driver clients if we simply upgraded our dependency. We don't want to
* increment our major version "just" for Guava (we have other changes planned).
*
* Therefore we depend on Guava 19, which has both the deprecated and the new APIs, and detect the actual version at
* runtime in order to call the relevant methods.
*
* This is a hack, and might not work with subsequent Guava releases; the real fix is to stop exposing Guava in our
* public API. We'll address that in version 4 of the driver.
*/
@SuppressWarnings("deprecation")
public abstract class GuavaCompatibility {
private static final Logger logger = LoggerFactory.getLogger(GuavaCompatibility.class);
/**
* The unique instance of this class, that is compatible with the Guava version found in the classpath.
*/
public static final GuavaCompatibility INSTANCE = selectImplementation();
/**
* Force the initialization of the class. This should be called early to ensure a fast failure if an incompatible
* version of Guava is in the classpath (the driver code calls it when loading the {@link Cluster} class).
*/
public static void init() {
// nothing to do, we just want the static initializers to run
}
/**
* Returns a {@code Future} whose result is taken from the given primary
* {@code input} or, if the primary input fails, from the {@code Future}
* provided by the {@code fallback}.
*
* @see Futures#withFallback(ListenableFuture, FutureFallback)
* @see Futures#catchingAsync(ListenableFuture, Class, AsyncFunction)
*/
public abstract ListenableFuture withFallback(ListenableFuture extends V> input,
AsyncFunction fallback);
/**
* Returns a {@code Future} whose result is taken from the given primary
* {@code input} or, if the primary input fails, from the {@code Future}
* provided by the {@code fallback}.
*
* @see Futures#withFallback(ListenableFuture, FutureFallback, Executor)
* @see Futures#catchingAsync(ListenableFuture, Class, AsyncFunction, Executor)
*/
public abstract ListenableFuture withFallback(ListenableFuture extends V> input,
AsyncFunction fallback, Executor executor);
/**
* Returns a new {@code ListenableFuture} whose result is asynchronously
* derived from the result of the given {@code Future}. More precisely, the
* returned {@code Future} takes its result from a {@code Future} produced by
* applying the given {@code AsyncFunction} to the result of the original
* {@code Future}.
*
* @see Futures#transform(ListenableFuture, AsyncFunction)
* @see Futures#transformAsync(ListenableFuture, AsyncFunction)
*/
public abstract ListenableFuture transformAsync(ListenableFuture input,
AsyncFunction super I, ? extends O> function);
/**
* Returns a new {@code ListenableFuture} whose result is asynchronously
* derived from the result of the given {@code Future}. More precisely, the
* returned {@code Future} takes its result from a {@code Future} produced by
* applying the given {@code AsyncFunction} to the result of the original
* {@code Future}.
*
* @see Futures#transform(ListenableFuture, AsyncFunction, Executor)
* @see Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)
*/
public abstract ListenableFuture transformAsync(ListenableFuture input,
AsyncFunction super I, ? extends O> function,
Executor executor);
/**
* Returns true if {@code target} is a supertype of {@code argument}. "Supertype" is defined
* according to the rules for type arguments introduced with Java generics.
*
* @see TypeToken#isAssignableFrom(Type)
* @see TypeToken#isSupertypeOf(Type)
*/
public abstract boolean isSupertypeOf(TypeToken> target, TypeToken> argument);
/**
* Returns an {@link Executor} that runs each task in the thread that invokes
* {@link Executor#execute execute}, as in {@link java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy}.
*
* @see MoreExecutors#sameThreadExecutor()
* @see MoreExecutors#directExecutor()
*/
public abstract Executor sameThreadExecutor();
private static GuavaCompatibility selectImplementation() {
if (isGuava_19_0_OrHigher()) {
logger.info("Detected Guava >= 19 in the classpath, using modern compatibility layer");
return new Version19OrHigher();
} else if (isGuava_16_0_1_OrHigher()) {
logger.info("Detected Guava < 19 in the classpath, using legacy compatibility layer");
return new Version18OrLower();
} else {
throw new DriverInternalError("Detected incompatible version of Guava in the classpath. " +
"You need 16.0.1 or higher.");
}
}
private static class Version18OrLower extends GuavaCompatibility {
@Override
public ListenableFuture withFallback(ListenableFuture extends V> input,
final AsyncFunction fallback) {
return Futures.withFallback(input, new FutureFallback() {
@Override
public ListenableFuture create(Throwable t) throws Exception {
return fallback.apply(t);
}
});
}
@Override
public ListenableFuture withFallback(ListenableFuture extends V> input,
final AsyncFunction fallback,
Executor executor) {
return Futures.withFallback(input, new FutureFallback() {
@Override
public ListenableFuture create(Throwable t) throws Exception {
return fallback.apply(t);
}
}, executor);
}
@Override
public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction super I, ? extends O> function) {
return Futures.transform(input, function);
}
@Override
public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction super I, ? extends O> function, Executor executor) {
return Futures.transform(input, function, executor);
}
@Override
public boolean isSupertypeOf(TypeToken> target, TypeToken> argument) {
return target.isAssignableFrom(argument);
}
@Override
public Executor sameThreadExecutor() {
return MoreExecutors.sameThreadExecutor();
}
}
private static class Version19OrHigher extends GuavaCompatibility {
@Override
public ListenableFuture withFallback(ListenableFuture extends V> input,
AsyncFunction fallback) {
return Futures.catchingAsync(input, Throwable.class, fallback);
}
@Override
public ListenableFuture withFallback(ListenableFuture extends V> input,
AsyncFunction fallback, Executor executor) {
return Futures.catchingAsync(input, Throwable.class, fallback, executor);
}
@Override
public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction super I, ? extends O> function) {
return Futures.transformAsync(input, function);
}
@Override
public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction super I, ? extends O> function, Executor executor) {
return Futures.transformAsync(input, function, executor);
}
@Override
public boolean isSupertypeOf(TypeToken> target, TypeToken> argument) {
return target.isSupertypeOf(argument);
}
@Override
public Executor sameThreadExecutor() {
return MoreExecutors.directExecutor();
}
}
private static boolean isGuava_19_0_OrHigher() {
return methodExists(Futures.class, "transformAsync", ListenableFuture.class, AsyncFunction.class);
}
private static boolean isGuava_16_0_1_OrHigher() {
// Cheap check for < 16.0
if (!methodExists(Maps.class, "asConverter", BiMap.class)) {
return false;
}
// More elaborate check to filter out 16.0, which has a bug in TypeToken. We need 16.0.1.
boolean resolved = false;
TypeToken