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

org.sosy_lab.common.Classes Maven / Gradle / Ivy

There is a newer version: 0.3000-609-g90a352c
Show newest version
/*
 *  SoSy-Lab Common is a library of useful utilities.
 *  This file is part of SoSy-Lab Common.
 *
 *  Copyright (C) 2007-2015  Dirk Beyer
 *  All rights reserved.
 *
 *  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 org.sosy_lab.common;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;

import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.reflect.TypeToken;

import org.sosy_lab.common.annotations.Unmaintained;
import org.sosy_lab.common.configuration.InvalidConfigurationException;
import org.sosy_lab.common.log.LogManager;

import java.lang.reflect.Constructor;
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.concurrent.Callable;
import java.util.logging.Level;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Helper class for various methods related to handling Java classes and types.
 */
public final class Classes {

  private Classes() {}

  /**
   * Exception thrown by {@link Classes#createInstance(Class, Class[], Object[], Class)}.
   */
  public static class ClassInstantiationException extends Exception {

    private static final long serialVersionUID = 7862065219560550275L;

    public ClassInstantiationException(String className, String msg, Throwable cause) {
      super("Cannot instantiate class " + className + ":" + msg, cause);
    }

    public ClassInstantiationException(String className, Throwable cause) {
      super("Cannot instantiate class " + className + ":" + cause.getMessage(), cause);
    }
  }

  /**
   * An exception that should be used if a checked exception is encountered in
   * a situation where it is not excepted
   * (e.g., when getting the result from a {@link Callable} of which you know
   * it shouldn't throw such exceptions).
   */
  public static final class UnexpectedCheckedException extends RuntimeException {

    private static final long serialVersionUID = -8706288432548996095L;

    public UnexpectedCheckedException(String message, Throwable source) {
      super(
          "Unexpected checked exception "
              + source.getClass().getSimpleName()
              + (isNullOrEmpty(message) ? "" : " during " + message)
              + (isNullOrEmpty(source.getMessage()) ? "" : ": " + source.getMessage()),
          source);

      assert (source instanceof Exception) && !(source instanceof RuntimeException);
    }
  }

  /**
   * Creates an instance of class cls, passing the objects from argumentList
   * to the constructor and casting the object to class type.
   *
   * @param cls The class to instantiate.
   * @param argumentTypes Array with the types of the parameters of the desired constructor.
   * @param argumentValues Array with the values that will be passed to the constructor.
   * @param type The return type (has to be a super type of the class, of course).
   * @throws ClassInstantiationException If something goes wrong
   * (like class cannot be found or has no constructor).
   * @throws InvocationTargetException If the constructor throws an exception.
   */
  public static  T createInstance(
      Class cls,
      @Nullable Class[] argumentTypes,
      @Nullable Object[] argumentValues,
      Class type)
      throws ClassInstantiationException, InvocationTargetException {
    checkNotNull(type);
    try {
      Constructor ct = cls.getConstructor(argumentTypes);
      return ct.newInstance(argumentValues);

    } catch (SecurityException e) {
      throw new ClassInstantiationException(cls.getCanonicalName(), e);
    } catch (NoSuchMethodException e) {
      throw new ClassInstantiationException(
          cls.getCanonicalName(), "Matching constructor not found!", e);
    } catch (InstantiationException e) {
      throw new ClassInstantiationException(cls.getCanonicalName(), e);
    } catch (IllegalAccessException e) {
      throw new ClassInstantiationException(cls.getCanonicalName(), e);
    }
  }

  /**
   * Creates an instance of class cls, passing the objects from argumentList
   * to the constructor and casting the object to class type.
   *
   * If there is no matching constructor or the the class cannot be instantiated,
   * an InvalidConfigurationException is thrown.
   *
   * @param type The return type (has to be a super type of the class, of course).
   * @param cls The class to instantiate.
   * @param argumentTypes Array with the types of the parameters
   * of the desired constructor (optional).
   * @param argumentValues Array with the values
   * that will be passed to the constructor.
   */
  public static  T createInstance(
      Class type,
      Class cls,
      @Nullable Class[] argumentTypes,
      Object[] argumentValues)
      throws InvalidConfigurationException {
    return createInstance(type, cls, argumentTypes, argumentValues, RuntimeException.class);
  }

  /**
   * Creates an instance of class cls, passing the objects from argumentList
   * to the constructor and casting the object to class type.
   *
   * If there is no matching constructor or the the class cannot be instantiated,
   * an InvalidConfigurationException is thrown.
   *
   * @param type The return type (has to be a super type of the class, of course).
   * @param cls The class to instantiate.
   * @param argumentTypes Array with the types of the parameters
   * of the desired constructor (optional).
   * @param argumentValues Array with the values that will be passed to the constructor.
   * @param exceptionType An exception type the constructor is allowed to throw.
   */
  public static  T createInstance(
      Class type,
      Class cls,
      @Nullable Class[] argumentTypes,
      Object[] argumentValues,
      Class exceptionType)
      throws X, InvalidConfigurationException {
    checkNotNull(exceptionType);
    if (argumentTypes == null) {
      // fill argumenTypes array
      argumentTypes = new Class[argumentValues.length];
      int i = 0;
      for (Object obj : argumentValues) {
        argumentTypes[i++] = obj.getClass();
      }

    } else {
      checkArgument(argumentTypes.length == argumentValues.length);
    }

    String className = cls.getSimpleName();
    String typeName = type.getSimpleName();

    // get constructor
    Constructor ct;
    try {
      ct = cls.getConstructor(argumentTypes);
    } catch (NoSuchMethodException e) {
      throw new InvalidConfigurationException(
          "Invalid " + typeName + " " + className + ", no matching constructor", e);
    }

    // verify signature
    String exception =
        Classes.verifyDeclaredExceptions(ct, exceptionType, InvalidConfigurationException.class);
    if (exception != null) {
      throw new InvalidConfigurationException(
          String.format(
              "Invalid %s %s, constructor declares unsupported checked exception %s.",
              typeName,
              className,
              exception));
    }

    // instantiate
    try {
      return ct.newInstance(argumentValues);

    } catch (InstantiationException e) {
      throw new InvalidConfigurationException(
          String.format(
              "Invalid %s %s, class cannot be instantiated (%s).",
              typeName,
              className,
              e.getMessage()),
          e);

    } catch (IllegalAccessException e) {
      throw new InvalidConfigurationException(
          "Invalid " + typeName + " " + className + ", constructor is not accessible", e);

    } catch (InvocationTargetException e) {
      Throwable t = e.getCause();
      Throwables.propagateIfPossible(t, exceptionType, InvalidConfigurationException.class);

      throw new UnexpectedCheckedException("instantiation of " + typeName + " " + className, t);
    }
  }

  /**
   * Similar to {@link Class#forName(String)}, but if the class is not found this
   * method re-tries with a package name prefixed.
   *
   * @param name The class name.
   * @param prefix An optional package name as prefix.
   * @return The class object for  name  or  prefix + "." + name
   * @throws ClassNotFoundException If none of the two classes can be found.
   */
  public static Class forName(String name, @Nullable String prefix)
      throws ClassNotFoundException, SecurityException {
    return forName(name, prefix, null);
  }

  /**
   * Similar to {@link Class#forName(String)} and {@link ClassLoader#loadClass(String)},
   * but if the class is not found this
   * method re-tries with a package name prefixed.
   *
   * @param name The class name.
   * @param prefix An optional package name as prefix.
   * @param cl An optional class loader to load the class (may be null).
   * @return The class object for  name  or  prefix + "." + name
   * @throws ClassNotFoundException If none of the two classes can be found.
   */
  private static Class forName(String name, @Nullable String prefix, @Nullable ClassLoader cl)
      throws ClassNotFoundException, SecurityException {
    if (cl == null) {
      // use the class loader of this class to simulate the behaviour
      // of Class#forName(String)
      cl = Classes.class.getClassLoader();
    }
    if (prefix == null || prefix.isEmpty()) {
      return cl.loadClass(name);
    }

    try {
      return cl.loadClass(name);

    } catch (ClassNotFoundException e) {
      try {
        return cl.loadClass(prefix + "." + name); // try with prefix added
      } catch (ClassNotFoundException e2) {
        e.addSuppressed(e2);
        throw e; // re-throw original exception to get correct error message
      }
    }
  }

  /**
   * Verify that a constructor declares no other checked exceptions except a
   * given type.
   *
   * Returns the name of any violating exception, or null if there is none.
   *
   * @param constructor The constructor to check.
   * @param allowedExceptionTypes The type of exception that is allowed.
   * @return Null or the name of a declared exception.
   */
  public static @Nullable String verifyDeclaredExceptions(
      Constructor constructor, Class... allowedExceptionTypes) {
    return verifyDeclaredExceptions(constructor.getExceptionTypes(), allowedExceptionTypes);
  }

  /**
   * Verify that a method declares no other checked exceptions except a
   * given type.
   *
   * Returns the name of any violating exception, or null if there is none.
   *
   * @param method The method to check.
   * @param allowedExceptionTypes The type of exception that is allowed.
   * @return Null or the name of a declared exception.
   */
  public static @Nullable String verifyDeclaredExceptions(
      Method method, Class... allowedExceptionTypes) {
    return verifyDeclaredExceptions(method.getExceptionTypes(), allowedExceptionTypes);
  }

  private static @Nullable String verifyDeclaredExceptions(
      Class[] declaredExceptionTypes, Class[] allowedExceptionTypes) {
    checkNotNull(allowedExceptionTypes);
    for (Class declaredException : declaredExceptionTypes) {

      if (Exception.class.isAssignableFrom(declaredException)) {
        // it's an exception, not an error

        if (Runtime.class.isAssignableFrom(declaredException)) {
          // it's a runtime exception
          continue;
        }

        boolean ok = false;
        for (Class allowedExceptionType : allowedExceptionTypes) {
          if (allowedExceptionType.isAssignableFrom(declaredException)) {
            ok = true;
            break;
          }
        }

        if (!ok) {
          return declaredException.getSimpleName();
        }
      }
    }
    return null;
  }

  /**
   * @see #getSingleTypeArgument(Type)
   */
  public static TypeToken getSingleTypeArgument(final TypeToken type) {
    return TypeToken.of(getSingleTypeArgument(type.getType()));
  }

  /**
   * From a type {@code X}, extract the {@code Foo}.
   * This is the value of {@link ParameterizedType#getActualTypeArguments()}.
   * This method also supports {@code X}, {@code X>} etc.
   *
   * 

Example results: * *

   * {@code
   * X          : Foo
   * X: Foo
   * X>     : Foo
   * }
   * 
* * @param type The type (needs to be parameterized with exactly one parameter) * @return A Type object. */ public static Type getSingleTypeArgument(final Type type) { checkNotNull(type); checkArgument( type instanceof ParameterizedType, "Cannot extract generic parameter from non-parameterized type %s", type); ParameterizedType pType = (ParameterizedType) type; Type[] parameterTypes = pType.getActualTypeArguments(); checkArgument( parameterTypes.length == 1, "Cannot extract generic parameter from parameterized type %s" + " which has not exactly one parameter", type); return extractUpperBoundFromType(parameterTypes[0]); } /** * Simplify a {@link Type} instance: if it is a wildcard generic type, replace * it with its upper bound. * * It does not support wildcards with several upper bounds. * * @param type A possibly generic type. * @return The type or its simplification. */ public static Type extractUpperBoundFromType(Type type) { checkNotNull(type); if (type instanceof WildcardType) { WildcardType wcType = (WildcardType) type; if (wcType.getLowerBounds().length > 0) { throw new UnsupportedOperationException( "Currently wildcard types with a lower bound like \"" + type + "\" are not supported "); } Type[] upperBounds = ((WildcardType) type).getUpperBounds(); if (upperBounds.length != 1) { throw new UnsupportedOperationException( "Currently only type bounds with one upper bound are supported, not \"" + type + "\""); } type = upperBounds[0]; } return type; } public static void produceClassLoadingWarning( LogManager logger, Class cls, @Nullable Class type) { checkNotNull(logger); Package pkg = cls.getPackage(); String typeName = type == null ? "class" : type.getSimpleName(); if (cls.isAnnotationPresent(Deprecated.class) || pkg.isAnnotationPresent(Deprecated.class)) { logger.logf( Level.WARNING, "Using %s %s, which is marked as deprecated and should not be used.", typeName, cls.getSimpleName()); } else if (cls.isAnnotationPresent(Unmaintained.class) || pkg.isAnnotationPresent(Unmaintained.class)) { logger.logf( Level.WARNING, "Using %s %s, which is unmaintained and may not work correctly.", typeName, cls.getSimpleName()); } } public static final Predicate> IS_GENERATED = new Predicate>() { @Override public boolean apply(@Nonnull Class pInput) { return pInput.getSimpleName().startsWith("AutoValue_"); } }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy