/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to You 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.apache.sling.testing.teleporter.client; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.apache.maven.shared.dependency.analyzer.asm.DependencyClassFileVisitor; import org.slf4j.Logger; /** Find the class dependencies of a class, recursively, * using the maven-dependency-analyzer and optionally considering only * specific package prefixes to avoid exploring the whole graph. */ class DependencyAnalyzer { private final Class<?> [] classes; private final Set<String> dependencyNames = new HashSet<String>(); private final Set<String> includes = new HashSet<String>(); private final Set<String> excludes = new HashSet<String>(); private final Set<Class<?>> alreadySeen = new HashSet<Class<?>>(); private Collection<Class<?>> dependencies; private DependencyAnalyzer(Class <?> ... classes) { this.classes = classes; } static DependencyAnalyzer forClass(Class<?> ... classes) { return new DependencyAnalyzer(classes); } /** Classes with names that match this prefix will be included, * unless they also match an exclude prefix. */ DependencyAnalyzer include(String prefix) { includes.add(prefix); return this; } /** Classes with names that match this prefix will not be included, * even if their name matches an include pattern */ DependencyAnalyzer exclude(String prefix) { excludes.add(prefix); return this; } /** Get the aggregate dependencies of our classes, based on a recursive * analysis that takes our include/exclude prefixes into account */ synchronized Collection<Class<?>> getDependencies(Logger log) { if(dependencies != null) { return dependencies; } dependencies = new HashSet<Class<?>>(); for(Class<?> c : classes) { analyze(c, log); } for(String dep : dependencyNames) { dependencies.add(toClass(dep)); } return dependencies; } /** Analyze a single class, recursively */ private void analyze(Class<?> c, Logger log) { if(alreadySeen.contains(c)) { return; } alreadySeen.add(c); final Set<String> deps = new HashSet<String>(); final String path = "/" + c.getName().replace('.', '/') + ".class"; final InputStream input = getClass().getResourceAsStream(path); if(input == null) { throw new RuntimeException("Class resource not found: " + path); } log.trace("Analyzing dependencies of {}...", c); try { try { final DependencyClassFileVisitor v = new DependencyClassFileVisitor(); v.visitClass(c.getName(), input); deps.addAll(v.getDependencies()); } finally { input.close(); } } catch(IOException ioe) { throw new RuntimeException("IOException while reading " + path); } // Keep only accepted dependencies, and recursively analyze them for(String dep : deps) { if(dep.equals(c.getName())) { continue; } if(accept(dep, log)) { dependencyNames.add(dep); analyze(toClass(dep), log); } } } /** True if given class name matches our include/exclude prefixes */ private boolean accept(String className, Logger log) { boolean result = false; for(String s : includes) { if(className.startsWith(s)) { result = true; break; } } // Excludes win over includes if(result) { for(String s : excludes) { if(className.startsWith(s)) { result = false; log.trace("Dependent class '{}' not included because package blacklisted via exclude '{}'.", className, s); break; } } } if (result) { log.trace("Dependent class '{}' included because package not whitelisted via include.", className); } else { log.trace("Dependent class '{}' not included because package not whitelisted via include.", className); } return result; } /** Convert a class name to its Class object */ private Class<?> toClass(String className) { try { return getClass().getClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new RuntimeException("Class not found :" + className, e); } } }