Package uk.ac.uea.threadr.util

Source Code of uk.ac.uea.threadr.util.ClassOperations

package uk.ac.uea.threadr.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Random;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Logger;

import uk.ac.uea.threadr.Threadr;
import uk.ac.uea.threadr.util.Operation.ConstructorOperation;
import uk.ac.uea.threadr.util.Operation.FieldOperation;
import uk.ac.uea.threadr.util.Operation.MethodOperation;

/**
* General operations that can be performed on Classes such as instantiation,
*  retrieval of constructors, methods and fields of certain types.
* @author Jordan Woerner
* @version 1.2
*/
public class ClassOperations {

  /** Logger for this class. */
  private static Logger logger = Threadr.logger;
 
  /** The maximum depth generating parameters can go before quitting. */
  final static int MAX_LEVEL = 5;
 
  /**
   * Attempts to create an instance of the provided class type. Automatically
   *  fails if the maximum recursion depth is reached.
   * 
   * @param classType The Class to try and instantiate.
   * @return An Object of the Class type required, or null if Class cannot be
   *  instantiated.
   * @throws InstantiationException Thrown if the Class cannot be
   * instantiated due to it being abstract, synthetic, an interface or enum.
   * Also thrown if recursion depth is too great.
   * @since 1.0
   */
  public static Object instantiateClass(Class<?> classType)
      throws InstantiationException
  {
   
    try {
      return instantiateClass(classType, 0);
    } catch (InstantiationException ex) {
      throw ex;
    }
  }
 
  /**
   * Attempts to create an instance of the provided class type. Automatically
   *  fails if the maximum recursion depth is reached.
   * 
   * @param classType The Class to try and instantiate.
   * @param level The current recursion depth.
   * @return An Object of the Class type required, or null if Class cannot be
   *  instantiated.
   * @throws InstantiationException Thrown if the class cannot be
   * instantiated due to it being abstract, synthetic, an interface or enum.
   * Also thrown if recursion depth is too great.
   * @since 1.0
   */
  static Object instantiateClass(Class<?> classType, int level)
      throws InstantiationException {
   
    /* Do not attempt to test abstract classes, interfaces, etc. */
    if (Modifier.isAbstract(classType.getModifiers())
        || classType.isInterface()
        || classType.isEnum()
        || classType.isSynthetic()) {
      throw new InstantiationException("Class impossible to instance.");
    }
   
    /* If we have recursed too deeply, just give up. */
    if (level > MAX_LEVEL) {
      throw new InstantiationException("Maximum instantiation depth reached!");
    }
   
    List<Operation> constructors = ClassOperations.getConstructingOperations(classType, classType);
    /* If there are no constructing operations, we can't do anything. */
    if (constructors.isEmpty()) return null;
   
    Object clazz = null;
    Random rand = new Random();
   
    while (clazz == null && !constructors.isEmpty()) {
      /* Pick and remove a random constructor from the list. */
      Operation constr = constructors.remove(rand.nextInt(constructors.size()));
     
      /* Log the constructor being tested. */
      if (level == 0)
        logger.fine(String.format("Trying '%s' to instantiate '%s'...", constr, classType.getCanonicalName()));
      /* Get all the parameter types needed for this Constructor. */
      Class<?>[] paramTypes = constr.getParamTypes();
      /* If there are no parameters, this array needs to be null. */
      Object[] params = null;
      if (paramTypes != null && paramTypes.length > 0) {
        /* If there are parameters needed, try and create them. */
        params = new Object[paramTypes.length];
       
        for (int j = 0; j < params.length; j++) {
          if (paramTypes[j].isArray()) {
            logger.finer(String.format("\t%s array",
                paramTypes[j].getComponentType().getCanonicalName(), params[j]));
            /* If the parameter is an array type, make an array. */
            params[j] = ParameterGen.createParameter(paramTypes[j], level, 2);
          } else {
            /* Otherwise just make an Object. */
            params[j] = ParameterGen.createParameter(paramTypes[j], level);
            logger.finer(String.format("\t%s - %s", paramTypes[j], params[j]));
          }
        }
      }
     
      /* Try and create a new instance of the reference. */
      try {
        clazz = constr.execute(clazz, params); // Use the chosen constructor.
      } catch (Exception ex) {
        logger.warning(ex.getMessage());
        clazz = null;
      }
     
      if (clazz == null) {
        logger.fine(String.format("Could not instantiate class %s using %s",
            classType.getCanonicalName(), constr));     
      } else {     
        logger.fine(String.format("Class '%s' instantiated using '%s'",
            classType.getCanonicalName(), constr));
      }
    }
   
    if (clazz == null) {
      /* If after all that, the class still isn't instanced. Try the
       * default constructor. This should call the default constructor
       * for java.lang.Object should none exist in the class. */
      try {
        clazz = classType.newInstance();
      } catch (IllegalAccessException iaEx) {
        logger.warning(String.format("Class '%s' is inaccesible", classType.getCanonicalName()));
        clazz = null;
      } catch (InstantiationException instEx) {
        logger.warning(String.format("Could not instantiate %s using default constructor", classType.getCanonicalName()));
        clazz = null;
      }
    }
   
    return clazz; // Return the constructed (or failed) object.
  }
 
  /**
   * <p>Look for any classes in the provided package and return their
   * {@link Class} objects in a {@link List}.</p>
   * <p>Checks for classes in the provided package and any sub-packages found
   * if requested.</p>
   *
   * @param pkg The Java package to search for Classes in.
   * @param subPackages Set to true to search sub-packages of the specified
   * package. False to ignore sub-packages.
   * @return A List of Class objects.
   * @since 1.0
   */
  public static List<Class<?>> findClassesInPackage(String pkg,
      boolean subPackages) {
   
    ClassLoader classLoader =
        Thread.currentThread().getContextClassLoader();
    List<Class<?>> classes = new ArrayList<>();
   
    if (classLoader != null) {
      String fileSep = System.getProperty("file.separator");
      String fixPkg = pkg.replace(".", fileSep);
      try {
        Enumeration<URL> resources = classLoader.getResources(fixPkg);
        List<File> dirs = new ArrayList<>();
        while (resources.hasMoreElements()) {
          URL resource = resources.nextElement();
          dirs.add(new File(resource.toURI()));
        }
       
        for (File dir : dirs) {
          classes.addAll(findClasses(dir, pkg, subPackages));
        }
      } catch (IOException ioEx) {
        logger.warning("IO Exception occured when loading classes from"
            + "package '" + pkg + "'!");
      } catch (URISyntaxException e) {
        e.printStackTrace();
      }
    }
   
    logger.info(String.format("%d classes found in package %s!",
        classes.size(), pkg));
   
    return classes;
  }
 
  /**
   * Find and return all Classes found in the provided package and all the
   * sub-packages of the package.
   *
   * @param dir The directory to find classes in.
   * @param pkg The package the classes belong to.
   * @param subPackages Set to true to search for Classes in sub-packages.
   * @return A {@link List} of {@link Class} objects.
   * @since 1.0
   */
  static List<Class<?>> findClasses(File dir, String pkg,
      boolean subPackages) {
   
    List<Class<?>> classes = new ArrayList<>();
   
    if (dir.exists()) {
      /* As long as the provided directory actually exists. */
      File[] files = dir.listFiles(); // Grab all the files.
     
      for (File file : files) {
        /* If the current file is another directory, check it. */
        if (subPackages
            &&file.isDirectory()
            && !file.getName().contains(".")) {
          String nPkg = String.format("%s.%s", pkg, file.getName());
          classes.addAll(findClasses(file, nPkg, true));
        } else if (file.getName().endsWith(".class")) {
          /* If it's a class instead, try and load it. */
          String fileSep = System.getProperty("file.separator");
          /* Fix up the package and class name to be used safely. */
          String classPath = String.format("%s%s%s", pkg, fileSep,
              file.getName().substring(0, file.getName().length()
                  - ".class".length()));
          classPath = classPath.replace(fileSep, ".");
          try {
            classes.add(Class.forName(classPath));
          } catch (ClassNotFoundException e) {
            logger.warning(String.format("Could not load class %s"
                + "\nReason: %s", classPath, e.getMessage()));
          }
        }
      }
    }
   
    return classes; // Return the List of found classes.
 
 
  /**
   * Gets a lust of all the classes contained inside the specified JAR file.
   * Classes in sub-packages will not be loaded.
   *
   * @param file The JAR file to read.
   * @return Returns an List of Class objectes type to wildcard. If a
   * file that isn't a JAR is provided, this will return null.
   * @since 1.2
   */
  public static List<Class<?>> getClasses(File file) {
   
    return getClasses(file, null, false);
  }
 
  /**
   * Gets a list of the classes contained inside the specified JAR file.
   * Allows for classes to be filtered by a package name. Classes in the
   * sub packages will not be loaded however.
   *
   * @param file The JAR file to read.
   * @param pkg The package to filter classes out by as a String.
   * @return Returns an List of Class objects typed to wildcard. If a
   * file that isn't a JAR is provided, this will return null.
   * @since 1.2
   */
  public static List<Class<?>> getClasses(File file, String pkg) {
   
    return getClasses(file, pkg, false);
  }
 
  /**
   * Gets a list of the classes contained inside the specified JAR file.
   * Allows for classes to be filtered by a package name. Classes in sub-
   * packages can be loaded by specifying 'true' as the final parameter of
   * this method.
   *
   * @param file The JAR file to read.
   * @param pkg The package to filter classes out by as a String.
   * @param subPkg Set to true to load classes found in the sub-packages
   * of the specified package.
   * @return Returns an List of Class objects typed to wildcard. If a
   * file that isn't a JAR is provided, this will return null.
   * @since 1.2
   */
  public static List<Class<?>> getClasses(File file, String pkg, boolean subPkg) {
   
    /* Stop now if the file provided is not a JAR file. */
    if (!file.getName().matches("[.\\/\\d\\w\\s-]+.jar"))
      return null;
   
    /* If a package name was provided, remove the dots. */
    if (pkg != null)
      pkg = pkg.replace(".", "/");
   
    List<Class<?>> classes = new ArrayList<>();
    JarInputStream jarStream = null;
    String className = null;
   
    try {
      /* Open up the JAR. */
      jarStream = new JarInputStream(new FileInputStream(file));
      JarEntry jarEntry = null;
     
      /* Look at all the files in the JAR file. Keep checking until
        nothing is left in the JarInputStream. */
      while ((jarEntry = jarStream.getNextJarEntry()) != null) {
        /* Add any classes found to the ArrayList. */
        if (jarEntry.getName().endsWith(".class")) {
          if (pkg == null || jarEntry.getName().startsWith(pkg)) {
            className = jarEntry.getName();
            /* If we aren't searching sub-packages, make sure we skip them. */
            if (!subPkg && !className.matches(pkg + "/[\\d\\w\\s-]+.class"))
              continue;
            /* Strip the '.class' from the class name to load it. */
            String strippedName = className.replace("/", ".") // Remove the dots
                .substring(0, className.length() - 6);
            /* Try and load the class. */
            Class<?> clazz = Class.forName(strippedName);
            classes.add(clazz); // Add it to the ArrayList.
          }
        }         
      }
    } catch(IOException ioEx) {
      /* Thrown if the JarInputStream fails. */
      logger.severe("Could not read JAR file - " + file.getName());
    } catch (ClassNotFoundException e) {
      /* Thrown if a class cannot be loaded. */
      logger.warning("Could not find class - " + className);
    } finally {
      /* Attempt to close the JarInputStream when we're done. */
      try {
        jarStream.close();
      } catch (IOException e) {
        logger.warning("Could not close jarStream.");
      } finally {
        jarStream = null;
      }
    }
   
    return classes;
  }
 
  /**
   * Gets a list of Operations that can construct the specified target class.
   * Should an Operation require the target type in its parameters, it is
   * excluded.
   *
   * @param clazz The Class to look for Operations in.
   * @param target The Class that Operations must construct.
   * @return An ArrayList of Operations that will construct the target Class.
   * @since 1.0
   */
  static List<Operation> getConstructingOperations(Class<?> clazz,
      Class<?> target) {
   
    List<Operation> operations = new ArrayList<>();
   
    /* Get all the Constructors, Methods and Fields from the class. */
    Constructor<?>[] constructors = clazz.getConstructors();
    Method[] methods = clazz.getMethods();
    Field[] fields = clazz.getFields();
   
    /* Add the constructors if the target is the same class. */
    if (clazz == target) {
      for (Constructor<?> c : constructors) {   
        boolean cyclic = false;
       
        Class<?>[] params = c.getParameterTypes();
        for (Class<?> param : params) {
          /* Make sure the parameters don't need the target type. */
          if (param == target || param.getComponentType() == target)
            cyclic = true;
        }
        /* If the constructor would cause more recursion, skip it. */
        if (cyclic) continue;
        /* Otherwise, add the constructor to the list. */
        operations.add(new Operation.ConstructorOperation(clazz, c));
      }
    }
    /* Add any useful methods. */
    for (Method m : methods) {
      if (Modifier.isPublic(m.getModifiers()) // Must be public.
          && !Modifier.isAbstract(m.getModifiers()) // Must not be abstract.
          && !m.isSynthetic() // Must not be synthetic.
          && m.getGenericReturnType() == target) // Must have the right return type.
      {
        boolean cyclic = false;
       
        Type[] params = m.getGenericParameterTypes();
        for (Type param : params) {
          /* Make sure the parameters don't need the target type. */
          if (param == target || param.getClass().getComponentType() == target)
            cyclic = true;
        }
        /* If the method would cause more recursion, skip it. */
        if (cyclic) continue;
        /* Add the method to the list of operations. */
        operations.add(new Operation.MethodOperation(clazz, m));
      }
    }
   
    /* Add any relevant accessible fields. */
    for (Field f : fields) {
      if (Modifier.isPublic(f.getModifiers()) // Must be public.
          && !Modifier.isAbstract(f.getModifiers()) // Must not be abstract.
          && !f.isSynthetic() // Must not be synthetic.
          && f.getType() == target) // Must have the right return type.
      {
        operations.add(new Operation.FieldOperation(clazz, f));
      }
    }
   
    return operations; // Return the list of Operations.
  }
 
  /**
   * Gets all declared constructors in a {@link Class} (including protected,
   * default and private visibility constructors) and converts them into
   * {@link ConstructorOperation}s.
   *
   * @param clazz The Class to gather constructors from.
   * @return An array of ConstructorOperations if any constructors exist in
   * the Class, otherwise null.
   * @since 1.1
   */
  public static final ConstructorOperation[] getConstructors(Class<?> clazz) {
   
    /* Get all the declared constructors in a Class. */
    Constructor<?>[] reflectedConstructors =
        clazz.getDeclaredConstructors();
    /* If there are none, just return null. */
    if (reflectedConstructors.length == 0) return new ConstructorOperation[0];
   
    /* Otherwise, convert them to ConstructorOperations. */
    ConstructorOperation[] constrs =
        new ConstructorOperation[reflectedConstructors.length];
    for (int i = 0; i < constrs.length; i++) {
      constrs[i] = new ConstructorOperation(clazz,
          reflectedConstructors[i]);
    }
   
    return constrs; // Return the ConstructorOperations.
  }
 
  /**
   * Gets all declared methods in a {@link Class} (including protected,
   * default and private visibility methods) and converts them into
   * {@link MethodOperation}s.
   *
   * @param clazz The Class to gather methods from.
   * @return An array of MethodOperations if any methods exist in the Class,
   * otherwise null.
   * @since 1.1
   */
  public static final MethodOperation[] getMethods(Class<?> clazz) {
   
    /* Get all the declared methods in the provided Class. */
    Method[] declaredMethods = clazz.getDeclaredMethods();
    if (declaredMethods.length == 0) return new MethodOperation[0];
   
    /* If there are methods, convert them to MethodOperations. */
    MethodOperation[] mthds = new MethodOperation[declaredMethods.length];
    for (int i = 0; i < mthds.length; i++) {
      mthds[i] = new MethodOperation(clazz, declaredMethods[i]);
    }
   
    return mthds; // Return the MethodOperations.
  }
 
  /**
   * Gets all declared fields in a {@link Class} (including protected,
   * default and private visibility fields) and converts them into
   * {@link FieldOperation}s.
   *
   * @param clazz The Class to gather fields from.
   * @return An array of FieldOperations if any fields exist in the Class,
   * otherwise null.
   * @since 1.1
   */
  public static final FieldOperation[] getFields(Class<?> clazz) {
   
    /* Get all the fields declared in the provided Class. */
    Field[] declaredFields = clazz.getDeclaredFields();
    if (declaredFields.length == 0) return new FieldOperation[0];
   
    /* If there are fields, convert them to FieldOperations. */
    FieldOperation[] fields = new FieldOperation[declaredFields.length];
    for (int i = 0; i < fields.length; i++) {
      fields[i] = new FieldOperation(clazz, declaredFields[i]);
    }
   
    return fields; // Return the FieldOperations.
  }
 
  /**
   * Searches for the specified Annotation on the provided class.
   *
   * @param clazz The Class to search for the Annotation on.
   * @param annotation The Annotation to look for as an instance of Class.
   * @return Returns true if the Annotation if found on the specified class, 
   * false otherwise.
   * @since 1.1
   */
  public static boolean hasAnnotation(Class<?> clazz,
      Class<? extends Annotation> annotation) {
   
    /* Loop through all the annotations found on the specified class. */
    for (Annotation a : clazz.getAnnotations()) {
      if (a.annotationType() == annotation) {
        /* If the current annotation matches the specified one. */
        logger.finer(String.format("Field '%s' has "
            + "'ThreadSafe' annotation.", clazz.getName()));
        return true; // The class must be marked.
      }
    }
   
    return false; // The annotation wasn't found.
  }
}
TOP

Related Classes of uk.ac.uea.threadr.util.ClassOperations

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.