package japicmp.config; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import japicmp.cli.JApiCli; import japicmp.cmp.JApiCmpArchive; import japicmp.exception.JApiCmpException; import japicmp.filter.*; import japicmp.model.AccessModifier; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; public class Options { private static final Logger LOGGER = Logger.getLogger(Options.class.getName()); static final String N_A = "n.a."; private List<JApiCmpArchive> oldArchives = new ArrayList<>(); private List<JApiCmpArchive> newArchives = new ArrayList<>(); private boolean outputOnlyModifications = false; private boolean outputOnlyBinaryIncompatibleModifications = false; private Optional<String> xmlOutputFile = Optional.absent(); private Optional<String> htmlOutputFile = Optional.absent(); private Optional<AccessModifier> accessModifier = Optional.of(AccessModifier.PROTECTED); private List<Filter> includes = new ArrayList<>(); private List<Filter> excludes = new ArrayList<>(); private boolean includeSynthetic = false; private IgnoreMissingClasses ignoreMissingClasses = new IgnoreMissingClasses(); private Optional<String> htmlStylesheet = Optional.absent(); private Optional<String> oldClassPath = Optional.absent(); private Optional<String> newClassPath = Optional.absent(); private JApiCli.ClassPathMode classPathMode = JApiCli.ClassPathMode.ONE_COMMON_CLASSPATH; private boolean noAnnotations = false; private boolean reportOnlyFilename; Options() { // intentionally left empty } public static Options newDefault() { return new Options(); } public void verify() { for (JApiCmpArchive archive : getOldArchives()) { verifyExistsCanReadAndJar(archive); } for (JApiCmpArchive archive : getNewArchives()) { verifyExistsCanReadAndJar(archive); } if (getHtmlOutputFile().isPresent()) { if (getHtmlStylesheet().isPresent()) { String pathname = getHtmlStylesheet().get(); File stylesheetFile = new File(pathname); if (!stylesheetFile.exists()) { throw JApiCmpException.cliError("HTML stylesheet '%s' does not exist.", pathname); } } } else { if (getHtmlStylesheet().isPresent()) { throw JApiCmpException.cliError("Define a HTML output file, if you want to apply a stylesheet."); } } if (getOldClassPath().isPresent() && getNewClassPath().isPresent()) { setClassPathMode(JApiCli.ClassPathMode.TWO_SEPARATE_CLASSPATHS); } else { if (getOldClassPath().isPresent() || getNewClassPath().isPresent()) { throw JApiCmpException.cliError("Please provide both options: " + JApiCli.OLD_CLASSPATH + " and " + JApiCli.NEW_CLASSPATH); } else { setClassPathMode(JApiCli.ClassPathMode.ONE_COMMON_CLASSPATH); } } } private static void verifyExistsCanReadAndJar(JApiCmpArchive jApiCmpArchive) { verifyExisting(jApiCmpArchive); verifyCanRead(jApiCmpArchive); verifyJarArchive(jApiCmpArchive); } private static void verifyExisting(JApiCmpArchive jApiCmpArchive) { if (!jApiCmpArchive.getFile().exists()) { throw JApiCmpException.cliError("File '%s' does not exist.", jApiCmpArchive.getFile().getAbsolutePath()); } } private static void verifyCanRead(JApiCmpArchive jApiCmpArchive) { if (!jApiCmpArchive.getFile().canRead()) { throw JApiCmpException.cliError("Cannot read file '%s'.", jApiCmpArchive.getFile().getAbsolutePath()); } } private static void verifyJarArchive(JApiCmpArchive jApiCmpArchive) { JarFile jarFile = null; try { jarFile = new JarFile(jApiCmpArchive.getFile()); } catch (IOException e) { throw JApiCmpException.cliError("File '%s' could not be opened as a jar file: %s", jApiCmpArchive.getFile().getAbsolutePath(), e.getMessage(), e); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException e) { LOGGER.log(Level.FINE, "Failed to close file: " + e.getLocalizedMessage(), e); } } } } public List<JApiCmpArchive> getNewArchives() { return newArchives; } public void setNewArchives(List<JApiCmpArchive> newArchives) { this.newArchives = newArchives; } public List<JApiCmpArchive> getOldArchives() { return oldArchives; } public void setOldArchives(List<JApiCmpArchive> oldArchives) { this.oldArchives = oldArchives; } public boolean isOutputOnlyModifications() { return outputOnlyModifications; } public void setOutputOnlyModifications(boolean outputOnlyModifications) { this.outputOnlyModifications = outputOnlyModifications; } public Optional<String> getXmlOutputFile() { return xmlOutputFile; } public void setXmlOutputFile(Optional<String> xmlOutputFile) { this.xmlOutputFile = xmlOutputFile; } public void setAccessModifier(Optional<AccessModifier> accessModifier) { this.accessModifier = accessModifier; } public AccessModifier getAccessModifier() { return accessModifier.get(); } public void setAccessModifier(AccessModifier accessModifier) { this.accessModifier = Optional.of(accessModifier); } public List<Filter> getIncludes() { return ImmutableList.copyOf(includes); } public List<Filter> getExcludes() { return ImmutableList.copyOf(excludes); } public void addExcludeFromArgument(Optional<String> packagesExcludeArg) { excludes = createFilterList(packagesExcludeArg, excludes, "Wrong syntax for exclude option '%s': %s"); } public void addIncludeFromArgument(Optional<String> packagesIncludeArg) { includes = createFilterList(packagesIncludeArg, includes, "Wrong syntax for include option '%s': %s"); } public List<Filter> createFilterList(Optional<String> argumentString, List<Filter> filters, String errorMessage) { for (String filterString : Splitter.on(";").trimResults().omitEmptyStrings().split(argumentString.or(""))) { try { // filter based on annotations if (filterString.startsWith("@")) { filters.add(new AnnotationClassFilter(filterString)); filters.add(new AnnotationFieldFilter(filterString)); filters.add(new AnnotationBehaviorFilter(filterString)); } if (filterString.contains("#")) { if (filterString.contains("(")) { JavadocLikeBehaviorFilter behaviorFilter = new JavadocLikeBehaviorFilter(filterString); filters.add(behaviorFilter); } else { JavadocLikeFieldFilter fieldFilter = new JavadocLikeFieldFilter(filterString); filters.add(fieldFilter); } } else { JavaDocLikeClassFilter classFilter = new JavaDocLikeClassFilter(filterString); filters.add(classFilter); JavadocLikePackageFilter packageFilter = new JavadocLikePackageFilter(filterString); filters.add(packageFilter); } } catch (Exception e) { throw new JApiCmpException(JApiCmpException.Reason.CliError, String.format(errorMessage, filterString, e.getMessage()), e); } } return filters; } public boolean isOutputOnlyBinaryIncompatibleModifications() { return outputOnlyBinaryIncompatibleModifications; } public void setOutputOnlyBinaryIncompatibleModifications(boolean outputOnlyBinaryIncompatibleModifications) { this.outputOnlyBinaryIncompatibleModifications = outputOnlyBinaryIncompatibleModifications; } public Optional<String> getHtmlOutputFile() { return htmlOutputFile; } public void setHtmlOutputFile(Optional<String> htmlOutputFile) { this.htmlOutputFile = htmlOutputFile; } public boolean isIncludeSynthetic() { return includeSynthetic; } public void setIncludeSynthetic(boolean showSynthetic) { this.includeSynthetic = showSynthetic; } public void setIgnoreMissingClasses(boolean ignoreMissingClasses) { this.ignoreMissingClasses.setIgnoreAllMissingClasses(ignoreMissingClasses); } public Optional<String> getHtmlStylesheet() { return htmlStylesheet; } public void setHtmlStylesheet(Optional<String> htmlStylesheet) { this.htmlStylesheet = htmlStylesheet; } public Optional<String> getOldClassPath() { return oldClassPath; } public void setOldClassPath(Optional<String> oldClassPath) { this.oldClassPath = oldClassPath; } public Optional<String> getNewClassPath() { return newClassPath; } public void setNewClassPath(Optional<String> newClassPath) { this.newClassPath = newClassPath; } public JApiCli.ClassPathMode getClassPathMode() { return classPathMode; } public void setClassPathMode(JApiCli.ClassPathMode classPathMode) { this.classPathMode = classPathMode; } public boolean isNoAnnotations() { return noAnnotations; } public void setNoAnnotations(boolean noAnnotations) { this.noAnnotations = noAnnotations; } public void addIgnoreMissingClassRegularExpression(String missingClassRegEx) { try { Pattern pattern = Pattern.compile(missingClassRegEx); this.ignoreMissingClasses.getIgnoreMissingClassRegularExpression().add(pattern); } catch (Exception e) { throw new JApiCmpException(JApiCmpException.Reason.IllegalArgument, "Could not compile provided regular expression: " + e.getMessage(), e); } } public IgnoreMissingClasses getIgnoreMissingClasses() { return ignoreMissingClasses; } public void setReportOnlyFilename(boolean reportOnlyFilename) { this.reportOnlyFilename = reportOnlyFilename; } public String getDifferenceDescription() { Joiner joiner = Joiner.on(";"); StringBuilder sb = new StringBuilder() .append("Comparing ") .append(isOutputOnlyBinaryIncompatibleModifications() ? "binary" :"source") .append(" compatibility of "); sb.append(joiner.join(toPathList(newArchives))); sb.append(" against "); sb.append(joiner.join(toPathList(oldArchives))); return sb.toString(); } private List<String> toPathList(List<JApiCmpArchive> archives) { List<String> paths = new ArrayList<>(archives.size()); for (JApiCmpArchive archive : archives) { if (this.reportOnlyFilename) { paths.add(archive.getFile().getName()); } else { paths.add(archive.getFile().getAbsolutePath()); } } return paths; } private List<String> toVersionList(List<JApiCmpArchive> archives) { List<String> versions = new ArrayList<>(archives.size()); for (JApiCmpArchive archive : archives) { String stringVersion = archive.getVersion().getStringVersion(); if (stringVersion != null) { versions.add(stringVersion); } } return versions; } public String joinOldArchives() { Joiner joiner = Joiner.on(";"); String join = joiner.join(toPathList(oldArchives)); if (join.trim().length() == 0) { return N_A; } return join; } public String joinNewArchives() { Joiner joiner = Joiner.on(";"); String join = joiner.join(toPathList(newArchives)); if (join.trim().length() == 0) { return N_A; } return join; } public String joinOldVersions() { Joiner joiner = Joiner.on(";"); String join = joiner.join(toVersionList(oldArchives)); if (join.trim().length() == 0) { return N_A; } return join; } public String joinNewVersions() { Joiner joiner = Joiner.on(";"); String join = joiner.join(toVersionList(newArchives)); if (join.trim().length() == 0) { return N_A; } return join; } }