mnaufaldillah

ClassFinder Jmeter

Oct 23rd, 2021
638
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to you under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  * http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17.  
  18. package org.apache.jorphan.reflect;
  19.  
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.lang.annotation.Annotation;
  23. import java.lang.reflect.Modifier;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Collection;
  27. import java.util.Collections;
  28. import java.util.HashSet;
  29. import java.util.List;
  30. import java.util.Set;
  31. import java.util.TreeSet;
  32. import java.util.stream.Stream;
  33. import java.util.zip.ZipEntry;
  34. import java.util.zip.ZipFile;
  35.  
  36. import org.slf4j.Logger;
  37. import org.slf4j.LoggerFactory;
  38.  
  39. /**
  40.  * This class finds classes that extend one of a set of parent classes
  41.  */
  42. public final class ClassFinder {
  43.     private static final Logger log = LoggerFactory.getLogger(ClassFinder.class);
  44.  
  45.     private static final String DOT_JAR = ".jar"; // $NON-NLS-1$
  46.     private static final String DOT_CLASS = ".class"; // $NON-NLS-1$
  47.     private static final int DOT_CLASS_LEN = DOT_CLASS.length();
  48.  
  49.     // static only
  50.     private ClassFinder() {
  51.     }
  52.  
  53.     /**
  54.      * Filter updates by only storing classes
  55.      * that extend one of the parent classes
  56.      */
  57.     private static class ExtendsClassFilter implements ClassFilter {
  58.  
  59.         private final Class<?>[] parents; // parent classes to check
  60.         private final boolean inner; // are inner classes OK?
  61.  
  62.         // hack to reduce the need to load every class in non-GUI mode, which only needs functions
  63.         // TODO perhaps use BCEL to scan class files instead?
  64.         private final String contains; // class name should contain this string
  65.         private final String notContains; // class name should not contain this string
  66.  
  67.         private final ClassLoader contextClassLoader
  68.             = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once
  69.  
  70.         ExtendsClassFilter(Class<?>[] parents, boolean inner, String contains, String notContains) {
  71.             this.parents = parents;
  72.             this.inner = inner;
  73.             this.contains = contains;
  74.             this.notContains = notContains;
  75.         }
  76.  
  77.         @Override
  78.         public boolean accept(String className) {
  79.             if (contains != null && !className.contains(contains)) {
  80.                 return false; // It does not contain a required string
  81.             }
  82.             if (notContains != null && className.contains(notContains)) {
  83.                 return false; // It contains a banned string
  84.             }
  85.             if (!className.contains("$") || inner) { // $NON-NLS-1$
  86.                 return isChildOf(parents, className, contextClassLoader);
  87.             }
  88.             return false;
  89.         }
  90.  
  91.         /**
  92.          * @param parentClasses      list of classes to check for
  93.          * @param strClassName       name of class to be checked
  94.          * @param contextClassLoader the classloader to use
  95.          * @return true if the class is a non-abstract, non-interface instance of at least one of the parent classes
  96.          */
  97.         private boolean isChildOf(
  98.                 Class<?>[] parentClasses, String strClassName, ClassLoader contextClassLoader) {
  99.             try {
  100.                 Class<?> targetClass = Class.forName(strClassName, false, contextClassLoader);
  101.  
  102.                 if (!targetClass.isInterface()
  103.                         && !Modifier.isAbstract(targetClass.getModifiers())) {
  104.                     return Arrays.stream(parentClasses)
  105.                             .anyMatch(parent -> parent.isAssignableFrom(targetClass));
  106.                 }
  107.             } catch (UnsupportedClassVersionError | ClassNotFoundException
  108.                     | NoClassDefFoundError | VerifyError e) {
  109.                 log.debug(e.getLocalizedMessage(), e);
  110.             }
  111.             return false;
  112.         }
  113.  
  114.         /* (non-Javadoc)
  115.          * @see java.lang.Object#toString()
  116.          */
  117.         @Override
  118.         public String toString() {
  119.             return "ExtendsClassFilter [parents=" +
  120.                     (parents != null ? Arrays.toString(parents) : "null") + ", inner=" + inner + ", contains="
  121.                     + contains + ", notContains=" + notContains + "]";
  122.         }
  123.     }
  124.  
  125.     private static class AnnoClassFilter implements ClassFilter {
  126.  
  127.         private final boolean inner; // are inner classes OK?
  128.  
  129.         private final Class<? extends Annotation>[] annotations; // annotation classes to check
  130.         private final ClassLoader contextClassLoader
  131.             = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once
  132.  
  133.         AnnoClassFilter(Class<? extends Annotation> []annotations, boolean inner){
  134.             this.annotations = annotations;
  135.             this.inner = inner;
  136.         }
  137.  
  138.         @Override
  139.         public boolean accept(String className) {
  140.             if (!className.contains("$") || inner) { // $NON-NLS-1$
  141.                 return hasAnnotationOnMethod(annotations,className, contextClassLoader);
  142.             }
  143.             return false;
  144.         }
  145.  
  146.         private boolean hasAnnotationOnMethod(
  147.                 Class<? extends Annotation>[] annotations,
  148.                 String classInQuestion,
  149.                 ClassLoader contextClassLoader) {
  150.             try {
  151.                 Class<?> c = Class.forName(classInQuestion, false, contextClassLoader);
  152.                 return Arrays.stream(c.getMethods())
  153.                         .anyMatch(method -> Arrays.stream(annotations).anyMatch(method::isAnnotationPresent));
  154.             } catch (NoClassDefFoundError | ClassNotFoundException | UnsupportedClassVersionError | VerifyError ignored) {
  155.                 log.debug(ignored.getLocalizedMessage(), ignored);
  156.             }
  157.             return false;
  158.         }
  159.  
  160.         /**
  161.          * @see java.lang.Object#toString()
  162.          */
  163.         @Override
  164.         public String toString() {
  165.             return "AnnoClassFilter [inner=" + inner + ", annotations=" +
  166.                     (annotations != null ? Arrays.toString(annotations) : "null")+ "]";
  167.         }
  168.     }
  169.  
  170.     /**
  171.      * Convenience method for
  172.      * {@link #findClassesThatExtend(String[], Class[], boolean)} with the
  173.      * option to include inner classes in the search set to false.
  174.      *
  175.      * @param paths        pathnames or jarfiles to search for classes
  176.      * @param superClasses required parent class(es)
  177.      * @return List of Strings containing discovered class names.
  178.      * @throws IOException when scanning the classes fails
  179.      */
  180.     public static List<String> findClassesThatExtend(String[] paths, Class<?>[] superClasses)
  181.             throws IOException {
  182.         return findClassesThatExtend(paths, superClasses, false);
  183.     }
  184.  
  185.     // For each directory in the search path, add all the jars found there
  186.     private static Set<File> addJarsInPath(String[] paths) {
  187.         Set<File> fullList = new HashSet<>();
  188.         for (final String path : paths) {
  189.             File dir = new File(path);
  190.             fullList.add(dir);
  191.             if (dir.exists() && dir.isDirectory()) {
  192.                 File[] jars = dir.listFiles(f -> f.isFile() && f.getName().endsWith(DOT_JAR));
  193.                 if (jars != null) {
  194.                     Collections.addAll(fullList, jars);
  195.                 }
  196.             }
  197.         }
  198.         return fullList;
  199.     }
  200.  
  201.     /**
  202.      * Find classes in the provided path(s)/jar(s) that extend the class(es).
  203.      *
  204.      * @param strPathsOrJars pathnames or jarfiles to search for classes
  205.      * @param superClasses   required parent class(es)
  206.      * @param innerClasses   should we include inner classes?
  207.      * @return List containing discovered classes
  208.      * @throws IOException when scanning for classes fails
  209.      */
  210.     public static List<String> findClassesThatExtend(String[] strPathsOrJars,
  211.             final Class<?>[] superClasses, final boolean innerClasses)
  212.             throws IOException  {
  213.         return findClassesThatExtend(strPathsOrJars,superClasses,innerClasses,null,null);
  214.     }
  215.  
  216.     /**
  217.      * Find classes in the provided path(s)/jar(s) that extend the class(es).
  218.      *
  219.      * @param strPathsOrJars pathnames or jarfiles to search for classes
  220.      * @param superClasses   required parent class(es)
  221.      * @param innerClasses   should we include inner classes?
  222.      * @param contains       classname should contain this string
  223.      * @param notContains    classname should not contain this string
  224.      * @return List containing discovered classes
  225.      * @throws IOException when scanning classes fails
  226.      */
  227.     public static List<String> findClassesThatExtend(String[] strPathsOrJars,
  228.             final Class<?>[] superClasses, final boolean innerClasses,
  229.             String contains, String notContains)
  230.             throws IOException  {
  231.         return findClassesThatExtend(strPathsOrJars, superClasses, innerClasses, contains, notContains, false);
  232.     }
  233.  
  234.     /**
  235.      * Find classes in the provided path(s)/jar(s) that extend the class(es).
  236.      *
  237.      * @param strPathsOrJars pathnames or jarfiles to search for classes
  238.      * @param annotations    required annotations
  239.      * @param innerClasses   should we include inner classes?
  240.      * @return List containing discovered classes
  241.      * @throws IOException when scanning classes fails
  242.      */
  243.     public static List<String> findAnnotatedClasses(String[] strPathsOrJars,
  244.             final Class<? extends Annotation>[] annotations, final boolean innerClasses)
  245.             throws IOException  {
  246.         return findClassesThatExtend(strPathsOrJars, annotations, innerClasses, null, null, true);
  247.     }
  248.  
  249.     /**
  250.      * Find classes in the provided path(s)/jar(s) that extend the class(es).
  251.      * Inner classes are not searched.
  252.      *
  253.      * @param strPathsOrJars pathnames or jarfiles to search for classes
  254.      * @param annotations    required annotations
  255.      * @return List containing discovered classes
  256.      * @throws IOException when scanning classes fails
  257.      */
  258.     public static List<String> findAnnotatedClasses(String[] strPathsOrJars,
  259.             final Class<? extends Annotation>[] annotations)
  260.             throws IOException  {
  261.         return findClassesThatExtend(strPathsOrJars, annotations, false, null, null, true);
  262.     }
  263.  
  264.     /**
  265.      * Find classes in the provided path(s)/jar(s) that extend the class(es).
  266.      *
  267.      * @param searchPathsOrJars pathnames or jarfiles to search for classes
  268.      * @param classNames        required parent class(es) or annotations
  269.      * @param innerClasses      should we include inner classes?
  270.      * @param contains          classname should contain this string
  271.      * @param notContains       classname should not contain this string
  272.      * @param annotations       true if classnames are annotations
  273.      * @return List containing discovered classes
  274.      * @throws IOException when scanning classes fails
  275.      */
  276.     public static List<String> findClassesThatExtend(String[] searchPathsOrJars,
  277.                 final Class<?>[] classNames, final boolean innerClasses,
  278.                 String contains, String notContains, boolean annotations)
  279.                 throws IOException  {
  280.         if (log.isDebugEnabled()) {
  281.             log.debug("findClassesThatExtend with searchPathsOrJars : {}, superclass : {}"+
  282.                     " innerClasses : {} annotations: {} contains: {}, notContains: {}",
  283.                     Arrays.toString(searchPathsOrJars),
  284.                     Arrays.toString(classNames),
  285.                     innerClasses, annotations,
  286.                     contains, notContains);
  287.         }
  288.  
  289.         ClassFilter filter;
  290.         if (annotations) {
  291.             @SuppressWarnings("unchecked")
  292.             // Should only be called with classes that extend annotations
  293.             final Class<? extends Annotation>[] annoclassNames = (Class<? extends Annotation>[]) classNames;
  294.             filter = new AnnoClassFilter(annoclassNames, innerClasses);
  295.         } else {
  296.             filter = new ExtendsClassFilter(classNames, innerClasses, contains, notContains);
  297.         }
  298.  
  299.         return findClasses(searchPathsOrJars, filter);
  300.     }
  301.  
  302.     /**
  303.      * Find all classes in the given jars that passes the class filter.
  304.      *
  305.      * @param searchPathsOrJars list of strings representing the jar locations
  306.      * @param filter            {@link ClassFilter} that the classes in the jars should
  307.      *                          conform to
  308.      * @return list of all classes in the jars, that conform to {@code filter}
  309.      * @throws IOException when reading the jar files fails
  310.      */
  311.     public static List<String> findClasses(String[] searchPathsOrJars, ClassFilter filter) throws IOException {
  312.         if (log.isDebugEnabled()) {
  313.             log.debug("findClasses with searchPathsOrJars : {} and classFilter : {}",
  314.                     Arrays.toString(searchPathsOrJars), filter);
  315.         }
  316.  
  317.         // Find all jars in the search path
  318.         Collection<File> strPathsOrJars = addJarsInPath(searchPathsOrJars);
  319.  
  320.         // Some of the jars might be out of classpath, however java.class.path does not represent
  321.         // the actual ClassLoader in use. For instance, NewDriver builds its own classpath
  322.  
  323.         Set<String> listClasses = new TreeSet<>();
  324.         // first get all the classes
  325.         for (File path : strPathsOrJars) {
  326.             findClassesInOnePath(path, listClasses, filter);
  327.         }
  328.  
  329.         if (log.isDebugEnabled()) {
  330.             log.debug("listClasses.size()={}", listClasses.size());
  331.             for (String clazz : listClasses) {
  332.                 log.debug("listClasses : {}", clazz);
  333.             }
  334.         }
  335.  
  336.         return new ArrayList<>(listClasses);
  337.     }
  338.  
  339.     /**
  340.      * Converts a class file from the text stored in a Jar file to a version
  341.      * that can be used in Class.forName().
  342.      *
  343.      * @param strClassName the class name from a Jar file
  344.      * @return String the Java-style dotted version of the name
  345.      */
  346.     private static String fixClassName(String strClassName) {
  347.         String fixedClassName = strClassName.replace('\\', '.'); // $NON-NLS-1$ // $NON-NLS-2$
  348.         fixedClassName = fixedClassName.replace('/', '.'); // $NON-NLS-1$ // $NON-NLS-2$
  349.         // remove ".class"
  350.         fixedClassName = fixedClassName.substring(0, fixedClassName.length() - DOT_CLASS_LEN);
  351.         return fixedClassName;
  352.     }
  353.  
  354.  
  355.     private static void findClassesInOnePath(File file, Set<String> listClasses, ClassFilter filter) throws IOException {
  356.         if (file.isDirectory()) {
  357.             findClassesInPathsDir(file.getAbsolutePath(), file, listClasses, filter);
  358.         } else if (file.exists()) {
  359.             try (ZipFile zipFile = new ZipFile(file);
  360.                  Stream<? extends ZipEntry> entries = zipFile.stream()) {
  361.                 entries.filter(entry -> entry.getName().endsWith(DOT_CLASS))
  362.                         .forEach(entry -> {
  363.                                     String fixedClassName = fixClassName(entry.getName());
  364.                                     applyFiltering(listClasses, filter, fixedClassName);
  365.                                 }
  366.                         );
  367.             } catch (IOException e) {
  368.                 log.warn("Can not open the jar {}, message: {}", file.getAbsolutePath(), e.getLocalizedMessage(), e);
  369.             }
  370.         }
  371.     }
  372.  
  373.  
  374.     private static void findClassesInPathsDir(String strPathElement, File dir, Set<String> listClasses, ClassFilter filter) throws IOException {
  375.         File[] list = dir.listFiles();
  376.         if (list == null) {
  377.             log.warn("{} is not a folder", dir.getAbsolutePath());
  378.             return;
  379.         }
  380.  
  381.         for (File file : list) {
  382.             if (file.isDirectory()) {
  383.                 // Recursive call
  384.                 findClassesInPathsDir(strPathElement, file, listClasses, filter);
  385.             } else if (file.getPath().endsWith(DOT_CLASS) && file.exists() && (file.length() != 0)) {
  386.                 final String path = file.getPath();
  387.                 String className = path.substring(strPathElement.length() + 1,
  388.                         path.lastIndexOf('.')) // $NON-NLS-1$
  389.                         .replace(File.separator.charAt(0), '.');// $NON-NLS-1$
  390.                 applyFiltering(listClasses, filter, className);
  391.             }
  392.         }
  393.     }
  394.  
  395.     /**
  396.      * Run {@link ClassFilter#accept(String)} on className and add to listClasses if accept returns true
  397.      * In case of Throwable, className will not be added
  398.      * @param classesSet Set of class names
  399.      * @param filter {@link ClassFilter}
  400.      * @param className Full class name
  401.      */
  402.     private static void applyFiltering(Set<String> classesSet, ClassFilter filter, String className) {
  403.         try {
  404.             if (filter.accept(className)) {
  405.                 classesSet.add(className);
  406.             }
  407.         } catch (Throwable e) { // NOSONAR : We need to trap also Errors
  408.             log.error("Error filtering class {}, it will be ignored", className, e);
  409.         }
  410.     }
  411.  
  412. }
RAW Paste Data