org.nickelproject.util.testUtil.ClasspathUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.nickelproject.util.testUtil.ClasspathUtil.java

Source

/*
 * Copyright (c) 2013 Numerate, Inc
 *
 * 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.nickelproject.util.testUtil;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import javassist.ClassPool;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.ClassFile;
import javassist.bytecode.annotation.Annotation;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.nickelproject.util.RethrownException;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

/**
 * A static utility class for getting resources from the class path.
 */
public final class ClasspathUtil {
    // The eclipse junit plugin appears to add to the classpath.
    // Unfortunately the classes contained on those paths are not accessible to
    // javassist
    // Therefore we filter them out.
    private static final String kEclipseFilter = "configuration/org.eclipse.osgi/bundles";

    private ClasspathUtil() {
        // Prevents construction.
    }

    /**
     * Find classes annotated with one or more of the arguments.
     *
     * @param pAnnotations
     *            The annotations to look for
     * @return An iterable of class names
     */
    public static Iterable<String> getAnnotatedClasses(final Class<?>... pAnnotations) {
        final String[] vAnnotationNames = new String[pAnnotations.length];
        for (int i = 0; i < vAnnotationNames.length; i++) {
            vAnnotationNames[i] = pAnnotations[i].getName();
        }
        final Iterable<String> vAllClasses = Iterables.transform(getResourcesOnClassPath("class"),
                new Function<String, String>() {
                    @Override
                    public String apply(final String pInput) {
                        return FilenameUtils.getBaseName(pInput);
                    }
                });
        return Iterables.filter(vAllClasses, Predicates.and(getIsConcrete(), new HasAnnotation(vAnnotationNames)));
    }

    /**
     * Gets all of the non-abstract, non-interface subclasses of the argument
     * classes. Useful for tagging interfaces.
     *
     * @param pTags
     *            An array of classes whose concrete descendants will be
     *            searched for
     * @return An iterable of class names
     */
    public static Iterable<String> getAllConcreteSubClasses(final Class<?>... pTags) {
        buildClassDag();
        final Iterable<String> vResults = getAllSubClasses(pTags);

        // Filters the results to ensure that the files are not abstract or interfaces.
        return Iterables.filter(vResults, getIsConcrete());
    }

    /**
     * Gets all of the resources on the class path with the provided extension.
     *
     * @param pExtension
     *            The extension string to look for
     * @return An iterable of strings containing the full resource name (i.e.,
     *         including extension)
     */
    public static Iterable<String> getResourcesOnClassPath(final String pExtension) {
        return Iterables.filter(getResourcesOnClassPath(), getHasExtension(pExtension));
    }

    /**
     * Gets all of the resources on the class path.
     *
     * @return An iterable of the names of all resources on the classpath
     */
    public static Iterable<String> getResourcesOnClassPath() {
        if (kAllResources == null) {
            kAllResources = Iterables.concat(
                    // Get all the resources from files
                    Iterables.concat(Iterables.transform(
                            Iterables.filter(getClassPath(), Predicates.not(getHasExtension("jar"))),
                            new GetFileFunction())),
                    // Get all the resources from JARs
                    Iterables.concat(Iterables.transform(Iterables.filter(getClassPath(), getHasExtension("jar")),
                            new GetJarEntries())));
        }
        return kAllResources;
    }

    private static Iterable<String> kAllResources = null;

    // This is the Class DAG
    private static Multimap<String, String> kParents = null;
    private static Multimap<String, String> kChildren = null;

    // Builds the inheritance DAG
    private static void buildClassDag() {
        if (kParents == null) {
            kParents = HashMultimap.create();
            kChildren = HashMultimap.create();
            for (final String vFullClassName : getResourcesOnClassPath("class")) {
                final String vClassName = FilenameUtils.getBaseName(vFullClassName);
                final ClassFile vClassFile = getClassFile(vClassName);
                final String vSuperClass = vClassFile.getSuperclass();
                kParents.put(vClassName, vSuperClass);
                kChildren.put(vSuperClass, vClassName);
                for (final String vInterface : vClassFile.getInterfaces()) {
                    kParents.put(vClassName, vInterface);
                    kChildren.put(vInterface, vClassName);
                }
            }
        }
    }

    // Performs a breadth first search of the inheritance DAG to find subclasses
    private static Iterable<String> getAllSubClasses(final Class<?>... pTags) {
        final Deque<String> vClassNames = Lists.newLinkedList();
        final Set<String> vResults = Sets.newHashSet();
        for (final Class<?> vClass : pTags) {
            vClassNames.add(vClass.getCanonicalName());
        }
        while (!vClassNames.isEmpty()) {
            final String vCurrentClass = vClassNames.pollFirst();
            for (final String vChild : kChildren.get(vCurrentClass)) {
                vClassNames.addLast(vChild);
                vResults.add(vChild);
            }
        }
        return vResults;
    }

    private static final class GetJarEntries implements Function<String, Iterable<String>> {
        @Override
        @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
        public Iterable<String> apply(final String input) {
            try {
                final JarFile vJarFile = new JarFile(input);
                final List<JarEntry> jarFileEntries = Collections.list(vJarFile.entries());
                vJarFile.close();
                return Iterables.transform(jarFileEntries, new Function<JarEntry, String>() {
                    @Override
                    public String apply(final JarEntry pInput2) {
                        return getResourceName(pInput2);
                    }
                });
            } catch (final IOException e) {
                throw RethrownException.rethrow(e);
            }
        }
    }

    private static final class GetFileFunction implements Function<String, Iterable<String>> {
        @Override
        @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
        public Iterable<String> apply(final String pName) {
            final File directory = new File(pName);
            return directory.isDirectory() ? Iterables.transform(FileUtils.listFiles(new File(pName), null, true),
                    new Function<File, String>() {
                        @Override
                        public String apply(final File input) {
                            return getResourceName(input, pName);
                        }
                    }) : Collections.<String>emptyList();
        }
    }

    private static Iterable<String> getClassPath() {
        // Removes some paths that eclipse adds during testing.
        return Iterables.filter(Lists.newArrayList(System.getProperty("java.class.path").split(File.pathSeparator)),
                Predicates.not(Predicates.containsPattern(Pattern.quote(kEclipseFilter))));
    }

    // Note that the ClassPool maintains ClassFiles obtained previously.
    private static ClassFile getClassFile(final String pResourceName) {
        try {
            return ClassPool.getDefault().get(pResourceName).getClassFile();
        } catch (final NotFoundException e) {
            throw RethrownException.rethrow(e);
        }
    }

    private static Predicate<String> getIsConcrete() {
        return new Predicate<String>() {
            @Override
            public boolean apply(final String pName) {
                return !getClassFile(pName).isAbstract() && !getClassFile(pName).isInterface();
            }
        };
    }

    private static Predicate<String> getHasExtension(final String pExtension) {
        return new Predicate<String>() {
            @Override
            public boolean apply(final String pName) {
                return FilenameUtils.isExtension(pName, pExtension);
            }
        };
    }

    private static final class HasAnnotation implements Predicate<String> {
        private final String[] mAnnotations;

        public HasAnnotation(final String... pAnnotations) {
            mAnnotations = pAnnotations;
        }

        @Override
        public boolean apply(final String pName) {
            final AttributeInfo vInfo = getClassFile(pName).getAttribute("RuntimeVisibleAnnotations");
            boolean vRetVal = false;
            if (vInfo != null) {
                for (final Annotation vAnnotation : ((AnnotationsAttribute) vInfo).getAnnotations()) {
                    for (final String vAnnotationQuery : mAnnotations) {
                        vRetVal |= vAnnotation.getTypeName().equals(vAnnotationQuery);
                    }
                }
            }
            return vRetVal;
        }
    }

    private static String getResourceName(final File pFile, final String pRoot) {
        final String vName = pFile.getAbsolutePath().substring(pRoot.length() + 1);
        return vName.replaceAll(Pattern.quote(File.separator), ".");
    }

    private static String getResourceName(final JarEntry pJarEntry) {
        return pJarEntry.getName().replaceAll("/", ".");
    }
}