/* * 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.maven; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.revapi.AnalysisContext; import org.revapi.Archive; import org.revapi.Difference; import org.revapi.DifferenceSeverity; import org.revapi.Element; import org.revapi.Report; import org.revapi.Reporter; /** * @author Lukas Krejci * @since 0.1 */ final class BuildTimeReporter implements Reporter { private final DifferenceSeverity breakingSeverity; private List<Report> allProblems; private List<Archive> oldApi; private List<Archive> newApi; public BuildTimeReporter(DifferenceSeverity breakingSeverity) { this.breakingSeverity = breakingSeverity; } public boolean hasBreakingProblems() { return allProblems != null && !allProblems.isEmpty(); } public String getAllProblemsMessage() { StringBuilder errors = new StringBuilder("The following API problems caused the build to fail:\n"); StringBuilder ignores = new StringBuilder(); for (Report r : allProblems) { Element element = r.getNewElement(); Archive archive; if (element == null) { element = r.getOldElement(); assert element != null; archive = shouldOutputArchive(oldApi, element.getArchive()) ? element.getArchive() : null; } else { archive = shouldOutputArchive(newApi, element.getArchive()) ? element.getArchive() : null; } for (Difference d : r.getDifferences()) { if (isReportable(d)) { errors.append(d.code).append(": ").append(element.getFullHumanReadableString()).append(": ") .append(d.description); if (archive != null) { errors.append(" [").append(archive.getName()).append("]"); } errors.append("\n"); ignores.append("{\n"); ignores.append(" \"code\": \"").append(d.code).append("\",\n"); if (r.getOldElement() != null) { ignores.append(" \"old\": \"").append(r.getOldElement()).append("\",\n"); } if (r.getNewElement() != null) { ignores.append(" \"new\": \"").append(r.getNewElement()).append("\",\n"); } for (Map.Entry<String, String> e : d.attachments.entrySet()) { ignores.append(" \"").append(e.getKey()).append("\": \"").append(e.getValue()).append("\",\n"); } ignores.append(" \"justification\": <<<<< ADD YOUR EXPLANATION FOR THE NECESSITY OF THIS CHANGE" + " >>>>>\n"); ignores.append("},\n"); } } } if (errors.length() == 0) { return null; } else { ignores.replace(ignores.length() - 2, ignores.length(), ""); return errors.toString() + "\nIf you're using the semver-ignore extension, update your module's version to one compatible " + "with the current changes (e.g. mvn package revapi:update-versions). If you want to " + "explicitly ignore this change and provide a justification for it, add the following JSON snippet " + "to your Revapi configuration under \"revapi.ignore\" path:\n" + ignores.toString(); } } @Nullable @Override public String[] getConfigurationRootPaths() { return null; } @Nullable @Override public Reader getJSONSchema(@Nonnull String configurationRootPath) { return null; } @Override public void initialize(@Nonnull AnalysisContext context) { allProblems = new ArrayList<>(); oldApi = new ArrayList<>(); for (Archive a : context.getOldApi().getArchives()) { oldApi.add(a); } newApi = new ArrayList<>(); for (Archive a : context.getNewApi().getArchives()) { newApi.add(a); } } @Override public void report(@Nonnull Report report) { Element element = report.getNewElement(); if (element == null) { element = report.getOldElement(); } if (element == null) { //wat? At least one of old and new should always be non-null return; } for (Difference d : report.getDifferences()) { if (isReportable(d)) { allProblems.add(report); break; } } } private boolean isReportable(Difference d) { DifferenceSeverity maxSeverity = DifferenceSeverity.NON_BREAKING; for (DifferenceSeverity s : d.classification.values()) { if (maxSeverity.compareTo(s) < 0) { maxSeverity = s; } } return maxSeverity.compareTo(breakingSeverity) >= 0; } private boolean shouldOutputArchive(List<Archive> primaryApi, Archive archive) { return !primaryApi.contains(archive) || primaryApi.size() > 1; } @Override public void close() throws IOException { } }