blob: 58ca0f90391052ef79620936632cf7df79a24300 [file] [log] [blame]
/*
* 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);
}
}
}