/*
* Copyright 2010 Henry Coles
*
* 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.pitest.dependency;
import static org.pitest.functional.prelude.Prelude.and;
import static org.pitest.functional.prelude.Prelude.not;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.objectweb.asm.ClassReader;
import org.pitest.bytecode.NullVisitor;
import org.pitest.classinfo.ClassByteArraySource;
import org.pitest.functional.F;
import org.pitest.functional.F2;
import org.pitest.functional.FCollection;
import org.pitest.functional.Option;
import org.pitest.functional.SideEffect1;
import org.pitest.functional.predicate.Predicate;
import org.pitest.util.Functions;
import org.pitest.util.Log;
public class DependencyExtractor {
private static final Logger LOG = Log.getLogger();
private final int depth;
private final ClassByteArraySource classToBytes;
public DependencyExtractor(final ClassByteArraySource classToBytes,
final int depth) {
this.depth = depth;
this.classToBytes = classToBytes;
}
@SuppressWarnings("unchecked")
public Collection<String> extractCallDependenciesForPackages(
final String clazz, final Predicate<String> targetPackages)
throws IOException {
final Set<String> allDependencies = extractCallDependencies(clazz,
new IgnoreCoreClasses());
return FCollection.filter(allDependencies,
and(asJVMNamePredicate(targetPackages), notSuppliedClass(clazz)));
}
private static F<String, Boolean> notSuppliedClass(final String clazz) {
return new F<String, Boolean>() {
@Override
public Boolean apply(final String a) {
return !Functions.jvmClassToClassName().apply(a).equals(clazz);
}
};
}
private static F<String, Boolean> asJVMNamePredicate(
final Predicate<String> predicate) {
return new F<String, Boolean>() {
@Override
public Boolean apply(final String a) {
return predicate.apply(Functions.jvmClassToClassName().apply(a));
}
};
}
public Collection<String> extractCallDependenciesForPackages(
final String clazz, final Predicate<String> targetPackages,
final Predicate<DependencyAccess> doNotTraverse) throws IOException {
final Set<String> allDependencies = extractCallDependencies(clazz,
doNotTraverse);
return FCollection.filter(allDependencies, targetPackages);
}
Set<String> extractCallDependencies(final String clazz,
final Predicate<DependencyAccess> filter) throws IOException {
return this
.extractCallDependencies(clazz, new TreeSet<String>(), filter, 0);
}
public int getMaxDistance() {
return this.depth;
}
private Set<String> extractCallDependencies(final String clazz,
final TreeSet<String> visited, final Predicate<DependencyAccess> filter,
final int currentDepth) throws IOException {
final Map<String, List<DependencyAccess>> classesToAccesses = groupDependenciesByClass(extractRelevantDependencies(
clazz, filter));
final Set<String> dependencies = new HashSet<String>(
classesToAccesses.keySet());
dependencies.removeAll(visited);
visited.addAll(dependencies);
if ((currentDepth < (this.depth - 1)) || (this.depth == 0)) {
dependencies.addAll(examineChildDependencies(currentDepth, dependencies,
visited, filter));
}
return dependencies;
}
private Set<String> examineChildDependencies(final int currentDepth,
final Set<String> classes, final TreeSet<String> visited,
final Predicate<DependencyAccess> filter) throws IOException {
final Set<String> deps = new HashSet<String>();
for (final String each : classes) {
final Set<String> childDependencies = extractCallDependencies(each,
visited, filter, currentDepth + 1);
deps.addAll(childDependencies);
}
return deps;
}
private Set<DependencyAccess> extractRelevantDependencies(final String clazz,
final Predicate<DependencyAccess> filter) throws IOException {
final List<DependencyAccess> dependencies = extract(clazz, filter);
final Set<DependencyAccess> relevantDependencies = new TreeSet<DependencyAccess>(
equalDestinationComparator());
FCollection.filter(dependencies, filter, relevantDependencies);
return relevantDependencies;
}
private static Comparator<DependencyAccess> equalDestinationComparator() {
return new Comparator<DependencyAccess>() {
@Override
public int compare(final DependencyAccess o1, final DependencyAccess o2) {
return o1.getDest().compareTo(o2.getDest());
}
};
}
@SuppressWarnings("unchecked")
private List<DependencyAccess> extract(final String clazz,
final Predicate<DependencyAccess> filter) throws IOException {
final Option<byte[]> bytes = this.classToBytes.getBytes(clazz);
if (bytes.hasNone()) {
LOG.warning("No bytes found for " + clazz);
return Collections.emptyList();
}
final ClassReader reader = new ClassReader(bytes.value());
final List<DependencyAccess> dependencies = new ArrayList<DependencyAccess>();
final SideEffect1<DependencyAccess> se = constructCollectingSideEffectForVisitor(
dependencies, and(not(nameIsEqual(clazz)), filter));
final DependencyClassVisitor dcv = new DependencyClassVisitor(
new NullVisitor(), se);
reader.accept(dcv, ClassReader.EXPAND_FRAMES);
return dependencies;
}
private Map<String, List<DependencyAccess>> groupDependenciesByClass(
final Set<DependencyAccess> relevantDependencies) {
final List<DependencyAccess> sortedByClass = new ArrayList<DependencyAccess>(
relevantDependencies.size());
Collections.sort(sortedByClass, classNameComparator());
return FCollection.fold(addDependenciesToMap(),
new HashMap<String, List<DependencyAccess>>(), relevantDependencies);
}
private static F2<Map<String, List<DependencyAccess>>, DependencyAccess, Map<String, List<DependencyAccess>>> addDependenciesToMap() {
return new F2<Map<String, List<DependencyAccess>>, DependencyAccess, Map<String, List<DependencyAccess>>>() {
@Override
public Map<String, List<DependencyAccess>> apply(
final Map<String, List<DependencyAccess>> map,
final DependencyAccess access) {
List<DependencyAccess> list = map.get(access.getDest().getOwner());
if (list == null) {
list = new ArrayList<DependencyAccess>();
}
list.add(access);
map.put(access.getDest().getOwner(), list);
return map;
}
};
}
private static Comparator<DependencyAccess> classNameComparator() {
return new Comparator<DependencyAccess>() {
@Override
public int compare(final DependencyAccess lhs, final DependencyAccess rhs) {
return lhs.getDest().getOwner().compareTo(rhs.getDest().getOwner());
}
};
}
private static Predicate<DependencyAccess> nameIsEqual(final String clazz) {
return new Predicate<DependencyAccess>() {
@Override
public Boolean apply(final DependencyAccess a) {
return a.getDest().getOwner().equals(clazz);
}
};
}
private static SideEffect1<DependencyAccess> constructCollectingSideEffectForVisitor(
final List<DependencyAccess> dependencies,
final Predicate<DependencyAccess> predicate) {
final SideEffect1<DependencyAccess> se = new SideEffect1<DependencyAccess>() {
@Override
public void apply(final DependencyAccess a) {
if (predicate.apply(a)) {
dependencies.add(a);
}
}
};
return se;
}
}