jatf.common.util.ArchitectureTestUtil.java Source code

Java tutorial

Introduction

Here is the source code for jatf.common.util.ArchitectureTestUtil.java

Source

/*
  This file is part of JATF.
  <p>
  JATF is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, version 3 of the License.
  <p>
  JATF is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  <p>
  You should have received a copy of the GNU General Public License
  along with JATF.  If not, see <http://www.gnu.org/licenses/>.
 */

package jatf.common.util;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.google.common.collect.Multimap;
import jatf.annotations.Dependency;
import jatf.annotations.Pattern;
import jatf.common.IArchitectureTest;
import jatf.common.rules.markers.ArchitectureTestMarker;
import jatf.common.rules.markers.ExcludeMarker;
import jatf.common.rules.markers.MustBePureMarker;
import jatf.common.rules.markers.MustExtendMarker;
import jatf.common.rules.markers.MustHaveAnnotationMarker;
import jatf.common.rules.markers.MustImplementMarker;
import jatf.common.rules.markers.MustNotExtendMarker;
import jatf.common.rules.markers.MustNotHaveAnnotationMarker;
import jatf.common.rules.markers.MustNotImplementMarker;
import jatf.common.rules.markers.MustNotOverrideMarker;
import jatf.common.rules.markers.MustNotReturnMarker;
import jatf.common.rules.markers.MustNotUseMarker;
import jatf.common.rules.markers.MustOverrideMarker;
import jatf.common.rules.markers.MustReturnMarker;
import jatf.common.rules.markers.MustUseMarker;
import jatf.common.rules.markers.RuleBasedMarker;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static jatf.common.ArchitectureTestConstraints.ROOT_FOLDER;
import static jatf.common.ArchitectureTestConstraints.SCOPES;
import static jatf.common.ArchitectureTestRunListener.report;

public class ArchitectureTestUtil {

    private static Map<String, String> sourceFiles;
    private static Set<URL> targetFolderUrls;
    private static Reflections reflections;

    @Nonnull
    public static Reflections buildReflections() {
        if (reflections == null) {
            initTargetFolderUrlSetIfNecessary();
            for (String scope : SCOPES) {
                targetFolderUrls.addAll(new Reflections(scope).getConfiguration().getUrls());
            }
            targetFolderUrls.addAll(new Reflections("jatf").getConfiguration().getUrls());
            URLClassLoader urlClassLoader = URLClassLoader
                    .newInstance(targetFolderUrls.toArray(new URL[targetFolderUrls.size()]));
            reflections = new Reflections(
                    new ConfigurationBuilder().setUrls(targetFolderUrls).addClassLoader(urlClassLoader));
        }
        return reflections;
    }

    public static void resetReflections() {
        targetFolderUrls = null;
        reflections = null;
        buildReflections();
    }

    public static void resetSourceFilesMap() {
        sourceFiles = null;
        try {
            initSourceFilesMapIfNecessary();
        } catch (IOException e) {
            report("Source files could not be mapped:", e);
        }
    }

    @Nonnull
    public static Set<String> getAllClassesInReflections(Reflections reflections) {
        Set<String> result = newHashSet();
        for (String key : reflections.getStore().keySet()) {
            Multimap<String, String> multimap = reflections.getStore().get(key);
            for (String name : multimap.keySet()) {
                Collection<String> collection = multimap.get(name);
                result.addAll(collection.stream().filter(item -> startsWithAnyOf(SCOPES, item))
                        .collect(Collectors.toList()));
            }
        }
        return result;
    }

    @Nonnull
    public static String assertMessage(@Nonnull Class<?> type) {
        return "Assertion not met in " + type.getName();
    }

    @Nonnull
    public static String assertMessage(@Nonnull Class<?> type, @Nonnull String details) {
        return assertMessage(type) + ": " + details;
    }

    public static void closeQuietly(@Nonnull Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException ignored) {
        }
    }

    @Nullable
    public static File findSourceFileFor(@Nonnull Class<?> clazz) {
        return findSourceFileFor(clazz.getName());
    }

    @Nullable
    public static File findSourceFileFor(@Nonnull String className) {
        try {
            initSourceFilesMapIfNecessary();
        } catch (IOException e) {
            report("Source files could not be mapped:", e);
            return null;
        }
        String path = sourceFiles.get(className);
        return path == null ? null : new File(path);
    }

    @SuppressWarnings("unused")
    @Nonnull
    public static <T> Set<Class<?>> getSubTypesOf(@Nonnull Class<T> superclass) {
        Set<Class<?>> entities = newHashSet();
        Reflections reflections = buildReflections();
        entities.addAll(reflections.getSubTypesOf(superclass));
        return entities;
    }

    @Nonnull
    public static String getPackageNameFor(@Nonnull String className) {
        return className.substring(0, className.lastIndexOf("."));
    }

    private static void initTargetFolderUrlSetIfNecessary() {
        if (targetFolderUrls == null) {
            targetFolderUrls = newHashSet();
            try {
                File path = new File(ROOT_FOLDER);
                report("Checking '" + path.getAbsolutePath() + "' for classes...");
                traverseForTargetFolderUrlsIn(path.getAbsolutePath(), targetFolderUrls);
            } catch (MalformedURLException e) {
                report("Failed to scan for targets paths:", e);
            }
            report("Added " + targetFolderUrls.size() + " locations for .class files to reflections repository.");
        }
    }

    private static void traverseForTargetFolderUrlsIn(@Nonnull String path, @Nonnull Set<URL> urls)
            throws MalformedURLException {
        File root = new File(path);
        File[] list = root.listFiles();
        if (list != null) {
            for (File file : list) {
                if (file.isDirectory()) {
                    if (file.getName().equalsIgnoreCase("classes") && file.getParentFile() != null
                            && file.getParentFile().exists()
                            && file.getParentFile().getName().equalsIgnoreCase("target")) {
                        urls.add(file.toURI().toURL());
                    } else {
                        traverseForTargetFolderUrlsIn(file.getAbsolutePath(), urls);
                    }
                }
            }
        }
    }

    private static void initSourceFilesMapIfNecessary() throws IOException {
        if (sourceFiles == null) {
            sourceFiles = newHashMap();
            File path = new File(ROOT_FOLDER);
            report("Checking '" + path.getAbsolutePath() + "' for source files...");
            traverseForJavaFilesIn(path.getAbsolutePath(), sourceFiles);
            report("Added " + sourceFiles.entrySet().size() + " files to source repository.");
        }
    }

    private static void traverseForJavaFilesIn(@Nonnull String path, @Nonnull Map<String, String> sourceFiles)
            throws IOException {
        File root = new File(path);
        File[] list = root.listFiles();
        if (list != null) {
            for (File file : list) {
                if (file.isDirectory()) {
                    traverseForJavaFilesIn(file.getAbsolutePath(), sourceFiles);
                } else {
                    String fileName = file.getName();
                    if (fileName.endsWith(".java")) {
                        sourceFiles.put(getClassNameFor(file), file.getAbsolutePath());
                    }
                }
            }
        }
    }

    @Nonnull
    private static String getClassNameFor(@Nonnull File file) throws IOException {
        StringBuilder result = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String sourceLine;
            String packageKeyword = "package ";
            while ((sourceLine = reader.readLine()) != null) {
                int indexOfPackage = sourceLine.indexOf(packageKeyword);
                int indexOfSemicolon = sourceLine.indexOf(";", indexOfPackage);
                if (indexOfPackage >= 0 && indexOfSemicolon >= packageKeyword.length()) {
                    result.append(sourceLine.substring(indexOfPackage + packageKeyword.length(), indexOfSemicolon));
                    result.append(".");
                    break;
                }
            }
            String fileName = file.getName();
            result.append(fileName.substring(0, fileName.indexOf('.')));
        }
        return result.toString();
    }

    @Nullable
    public static <M extends RuleBasedMarker> M createAnnotation(@Nonnull Class<M> markerType,
            final Map<String, List<?>> fields) {
        M marker = null;
        try {
            marker = markerType.newInstance();
            if (marker instanceof ArchitectureTestMarker) {
                ((ArchitectureTestMarker) marker).omitConventions = Boolean
                        .parseBoolean(fields.get("omitConventions").get(0).toString());
                ((ArchitectureTestMarker) marker).omitMetrics = Boolean
                        .parseBoolean(fields.get("omitMetrics").get(0).toString());
                ((ArchitectureTestMarker) marker).dependencies = convertToDependencyArray(
                        fields.get("dependencies"));
                ((ArchitectureTestMarker) marker).patterns = convertToPatternArray(fields.get("patterns"));
                ((ArchitectureTestMarker) marker).enforceSecurityTests = Boolean
                        .parseBoolean(fields.get("enforceSecurityTests").get(0).toString());
            } else if (marker instanceof MustBePureMarker) {
                ((MustBePureMarker) marker).degree = Double.parseDouble(fields.get("degree").get(0).toString());
            } else if (marker instanceof MustExtendMarker) {
                ((MustExtendMarker) marker).type = (Class<?>) fields.get("type").get(0);
            } else if (marker instanceof MustImplementMarker) {
                ((MustImplementMarker) marker).interfaces = convertToTypeArray(fields.get("interfaces"));
            } else if (marker instanceof MustNotExtendMarker) {
                ((MustNotExtendMarker) marker).type = (Class<?>) fields.get("type").get(0);
            } else if (marker instanceof MustNotImplementMarker) {
                ((MustNotImplementMarker) marker).interfaces = convertToTypeArray(fields.get("interfaces"));
            } else if (marker instanceof MustNotOverrideMarker) {
                ((MustNotOverrideMarker) marker).methodNames = convertToStringArray(fields.get("methodNames"));
            } else if (marker instanceof MustNotUseMarker) {
                ((MustNotUseMarker) marker).types = convertToTypeArray(fields.get("types"));
            } else if (marker instanceof MustOverrideMarker) {
                ((MustOverrideMarker) marker).methodNames = convertToStringArray(fields.get("methodNames"));
            } else if (marker instanceof MustUseMarker) {
                ((MustUseMarker) marker).types = convertToTypeArray(fields.get("types"));
            } else if (marker instanceof MustHaveAnnotationMarker) {
                //noinspection unchecked
                ((MustHaveAnnotationMarker) marker).annotation = (Class<? extends Annotation>) fields
                        .get("annotation").get(0);
            } else if (marker instanceof MustNotHaveAnnotationMarker) {
                //noinspection unchecked
                ((MustNotHaveAnnotationMarker) marker).annotation = (Class<? extends Annotation>) fields
                        .get("annotation").get(0);
            } else if (marker instanceof MustReturnMarker) {
                ((MustReturnMarker) marker).types = convertToTypeArray(fields.get("types"));
            } else if (marker instanceof MustNotReturnMarker) {
                ((MustNotReturnMarker) marker).types = convertToTypeArray(fields.get("types"));
            } else if (marker instanceof ExcludeMarker) {
                ((ExcludeMarker) marker).tests = convertToArchitectureTestTypeArray(fields.get("tests"));
            }
        } catch (Exception e) {
            report("RuleBasedAnnotation " + markerType + " could not be created:", e);
        }
        return marker;
    }

    @Nullable
    public static <M extends RuleBasedMarker> M createAnnotation(@Nonnull Class<M> markerType,
            final String argumentName, final List<?> values) {
        Map<String, List<?>> fields = newHashMap();
        fields.put(argumentName, values);
        return createAnnotation(markerType, fields);
    }

    @Nullable
    public static ArchitectureTestMarker createArchitectureTestAnnotation(boolean omitMetrics,
            boolean omitConventions, Pattern[] patterns, Dependency[] dependencies, boolean enforceSecurityTests) {
        return createAnnotation(ArchitectureTestMarker.class, fieldsForArchitectureTestAnnotation(omitMetrics,
                omitConventions, patterns, dependencies, enforceSecurityTests));
    }

    public static <V extends VoidVisitorAdapter<?>> void parseWithVoidVisitor(@Nonnull Class<?> clazz,
            @Nonnull V visitor) {
        File sourceFile = findSourceFileFor(clazz);
        if (sourceFile != null) {
            parseWithVoidVisitor(clazz.getName(), visitor, sourceFile);
        }
    }

    public static <V extends VoidVisitorAdapter<?>> void parseWithVoidVisitor(@Nonnull String className,
            @Nonnull V visitor) {
        File sourceFile = findSourceFileFor(className);
        if (sourceFile != null) {
            parseWithVoidVisitor(className, visitor, sourceFile);
        }
    }

    private static <V extends VoidVisitorAdapter<?>> void parseWithVoidVisitor(String className, V visitor,
            File sourceFile) {
        try {
            CompilationUnit compilationUnit = JavaParser.parse(sourceFile);
            //noinspection unchecked
            visitor.visit(compilationUnit, null);
        } catch (IOException e) {
            report("Could not open source file for class " + className, e);
        }
    }

    public static <V extends VoidVisitorAdapter<?>> void parseWithVoidVisitor(@Nonnull BlockStmt blockStmt,
            @Nonnull V visitor) {
        visitor.visit(blockStmt, null);
    }

    public static <V extends VoidVisitorAdapter<?>> void parseWithVoidVisitor(@Nonnull AssignExpr assignExpr,
            @Nonnull V visitor) {
        visitor.visit(assignExpr, null);
    }

    @Nonnull
    private static Map<String, List<?>> fieldsForArchitectureTestAnnotation(boolean omitMetrics,
            boolean omitConventions, Pattern[] patterns, Dependency[] dependencies, boolean enforceSecurityTests) {
        Map<String, List<?>> fields = newHashMap();
        fields.put("omitMetrics", Collections.singletonList(omitMetrics));
        fields.put("omitConventions", Collections.singletonList(omitConventions));
        fields.put("patterns", Arrays.asList(patterns));
        fields.put("dependencies", Arrays.asList(dependencies));
        fields.put("enforceSecurityTests", Collections.singletonList(enforceSecurityTests));
        return fields;
    }

    private static String[] convertToStringArray(List<?> items) {
        String[] result = new String[items.size()];
        int i = 0;
        for (Object object : items) {
            result[i++] = (String) object;
        }
        return result;
    }

    private static Class<?>[] convertToTypeArray(List<?> items) {
        Class<?>[] result = new Class<?>[items.size()];
        int i = 0;
        for (Object object : items) {
            result[i++] = (Class<?>) object;
        }
        return result;
    }

    private static Pattern[] convertToPatternArray(List<?> items) {
        Pattern[] result = new Pattern[items.size()];
        int i = 0;
        for (Object object : items) {
            result[i++] = (Pattern) object;
        }
        return result;
    }

    private static Dependency[] convertToDependencyArray(List<?> items) {
        Dependency[] result = new Dependency[items.size()];
        int i = 0;
        for (Object object : items) {
            result[i++] = (Dependency) object;
        }
        return result;
    }

    private static Class<? extends IArchitectureTest>[] convertToArchitectureTestTypeArray(List<?> items) {
        Class<?>[] result = new Class<?>[items.size()];
        int i = 0;
        for (Object object : items) {
            result[i++] = (Class<?>) object;
        }
        //noinspection unchecked
        return (Class<? extends IArchitectureTest>[]) result;
    }

    private static boolean startsWithAnyOf(String[] prefixes, @Nonnull String s) {
        for (String prefix : prefixes) {
            if (s.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }
}