All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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 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 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 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 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 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 function) { return Futures.transform(input, function); } @Override public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction 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 input, AsyncFunction fallback) { return Futures.catchingAsync(input, Throwable.class, fallback); } @Override public ListenableFuture withFallback(ListenableFuture input, AsyncFunction fallback, Executor executor) { return Futures.catchingAsync(input, Throwable.class, fallback, executor); } @Override public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction function) { return Futures.transformAsync(input, function); } @Override public ListenableFuture transformAsync(ListenableFuture input, AsyncFunction 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> mapOfString = TypeTokens.mapOf(String.class, String.class); Type type = mapOfString.getType(); if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; Type[] types = pType.getActualTypeArguments(); if (types.length == 2) { TypeToken valueType = TypeToken.of(types[1]); resolved = valueType.getRawType().equals(String.class); } } if (!resolved) { logger.debug("Detected Guava issue #1635 which indicates that version 16.0 is in the classpath"); } return resolved; } private static boolean methodExists(Class declaringClass, String methodName, Class... parameterTypes) { try { declaringClass.getMethod(methodName, parameterTypes); return true; } catch (Exception e) { logger.debug("Error while checking existence of method " + declaringClass.getSimpleName() + "." + methodName, e); return false; } } }