/*
* Copyright 2014 Lukas Krejci
*
* 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.revapi.java;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.revapi.API;
import org.revapi.AnalysisContext;
import org.revapi.Archive;
import org.revapi.Difference;
import org.revapi.Report;
import org.revapi.Reporter;
import org.revapi.Revapi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Lukas Krejci
* @since 0.1
*/
public abstract class AbstractJavaElementAnalyzerTest {
protected boolean containsDifference(List<Report> problems, String oldElement, String newElement,
String differenceCode) {
for (Report r : problems) {
boolean oldTypeMatches = oldElement == null ? r.getOldElement() == null :
r.getOldElement() != null && oldElement.equals(r.getOldElement().getFullHumanReadableString());
boolean newTypeMatches = newElement == null ? r.getNewElement() == null :
r.getNewElement() != null && newElement.equals(r.getNewElement().getFullHumanReadableString());
boolean problemMatches = false;
for (Difference p : r.getDifferences()) {
if (differenceCode.equals(p.code)) {
problemMatches = true;
break;
}
}
if (oldTypeMatches && newTypeMatches && problemMatches) {
return true;
}
}
return false;
}
protected final static class ShrinkwrapArchive implements Archive {
private final JavaArchive archive;
protected ShrinkwrapArchive(JavaArchive archive) {
this.archive = archive;
}
@Nonnull
@Override
public String getName() {
return archive.getName();
}
@Nonnull
@Override
public InputStream openStream() throws IOException {
return archive.as(ZipExporter.class).exportAsInputStream();
}
}
protected static class ProblemOccurrenceReporter implements Reporter {
private static final Logger LOG = LoggerFactory.getLogger("Problem");
private final Map<String, Integer> problemCounters;
public ProblemOccurrenceReporter() {
this.problemCounters = new HashMap<>();
}
@Nullable
@Override
public String[] getConfigurationRootPaths() {
return null;
}
@Nullable
@Override
public Reader getJSONSchema(@Nonnull String configurationRootPath) {
return null;
}
@Override
public void initialize(@Nonnull AnalysisContext properties) {
}
@Override
public void report(@Nonnull Report report) {
for (Difference d : report.getDifferences()) {
Integer cnt = problemCounters.get(d.code);
if (cnt == null) {
cnt = 1;
} else {
cnt += 1;
}
problemCounters.put(d.code, cnt);
String oldE = report.getOldElement() == null
? "<none>"
: report.getOldElement().getFullHumanReadableString();
String newE = report.getNewElement() == null
? "<none>"
: report.getNewElement().getFullHumanReadableString();
LOG.info("[" + d.code + "] old: " + oldE + ", new: " + newE + ", " + d.classification + ", " + d.description);
}
}
@Override
public void close() throws IOException {
}
public Map<String, Integer> getProblemCounters() {
return problemCounters;
}
}
protected final static class ArchiveAndCompilationPath {
final JavaArchive archive;
final Path compilationPath;
private ArchiveAndCompilationPath(JavaArchive archive, Path compilationPath) {
this.archive = archive;
this.compilationPath = compilationPath;
}
}
private static final Logger LOG = LoggerFactory.getLogger("TestWatch");
@Rule
public final TestRule watcher = new TestWatcher() {
@Override
protected void starting(Description description) {
LOG.info(description.getDisplayName() + " starts");
}
@Override
protected void finished(Description description) {
LOG.info(description.getDisplayName() + " finished");
}
};
@SuppressWarnings("ConstantConditions")
protected ArchiveAndCompilationPath createCompiledJar(String jarName, String... sourceFiles) throws Exception {
File targetPath = Files.createTempDirectory("element-analyzer-test-" + jarName + ".jar-").toAbsolutePath()
.toFile();
List<String> options = Arrays.asList("-d", targetPath.getAbsolutePath());
List<JavaFileObject> sources = new ArrayList<>();
for (String source : sourceFiles) {
sources.add(new SourceInClassLoader(source));
}
if (!ToolProvider.getSystemJavaCompiler().getTask(null, null, null, options, null, sources).call()) {
throw new IllegalStateException("Failed to compile source files: " + Arrays.asList(sourceFiles));
}
JavaArchive archive = ShrinkWrap.create(JavaArchive.class, jarName + ".jar");
for (File f : targetPath.listFiles()) {
archive.addAsResource(f);
}
return new ArchiveAndCompilationPath(archive, targetPath.toPath());
}
protected Revapi createRevapi(Reporter testReporter) {
return Revapi.builder().withAnalyzers(new JavaApiAnalyzer()).withReporters(testReporter)
.withTransformsFromThreadContextClassLoader().withFiltersFromThreadContextClassLoader().build();
}
protected void runAnalysis(Reporter testReporter, String v1Source, String v2Source) throws Exception {
runAnalysis(testReporter, null, v1Source, v2Source);
}
protected void runAnalysis(Reporter testReporter, String configJSON, String v1Source, String v2Source)
throws Exception {
runAnalysis(testReporter, configJSON, new String[]{v1Source}, new String[]{v2Source});
}
protected void runAnalysis(Reporter testReporter, String[] v1Source, String[] v2Source) throws Exception {
runAnalysis(testReporter, null, v1Source, v2Source);
}
protected void runAnalysis(Reporter testReporter, String configurationJSON, String[] v1Source, String[] v2Source)
throws Exception {
ArchiveAndCompilationPath v1Archive = createCompiledJar("v1", v1Source);
ArchiveAndCompilationPath v2Archive = createCompiledJar("v2", v2Source);
try (Revapi revapi = createRevapi(testReporter)) {
AnalysisContext.Builder bld = AnalysisContext.builder()
.withOldAPI(API.of(new ShrinkwrapArchive(v1Archive.archive)).build())
.withNewAPI(API.of(new ShrinkwrapArchive(v2Archive.archive)).build());
if (configurationJSON != null) {
bld.withConfigurationFromJSON(configurationJSON);
}
AnalysisContext ctx = bld.build();
revapi.validateConfiguration(ctx);
revapi.analyze(ctx);
} finally {
deleteDir(v1Archive.compilationPath);
deleteDir(v2Archive.compilationPath);
}
}
protected static void deleteDir(final Path path) throws IOException {
try {
Files.walkFileTree(path, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
if (path.equals(file)) {
return FileVisitResult.CONTINUE;
}
throw new IOException("Failed to delete file '" + file + "'.", exc);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new IllegalStateException("Failed to remove compiled results", e);
}
}
protected static class CollectingReporter implements Reporter {
private final List<Report> allReports;
protected CollectingReporter(List<Report> allReports) {
this.allReports = allReports;
}
@Nullable
@Override
public String[] getConfigurationRootPaths() {
return null;
}
@Nullable
@Override
public Reader getJSONSchema(@Nonnull String configurationRootPath) {
return null;
}
@Override
public void initialize(@Nonnull AnalysisContext properties) {
}
@Override
public void report(@Nonnull Report report) {
if (!report.getDifferences().isEmpty()) {
allReports.add(report);
}
}
@Override
public void close() throws IOException {
}
}
}