Licenses.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.rat.help;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.lang3.StringUtils;
import org.apache.rat.ConfigurationException;
import org.apache.rat.ReportConfiguration;
import org.apache.rat.analysis.IHeaderMatcher;
import org.apache.rat.config.parameters.ComponentType;
import org.apache.rat.config.parameters.Description;
import org.apache.rat.config.parameters.DescriptionBuilder;
import org.apache.rat.configuration.MatcherBuilderTracker;
import org.apache.rat.configuration.builders.AbstractBuilder;
import org.apache.rat.license.ILicense;
import org.apache.rat.license.ILicenseFamily;
import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
import static java.lang.String.format;
/**
* Generates text based documentation for Licenses, LicenceFamilies, and Matchers.
* Utilizes the same command line as the CLI based Report client so that additional licenses, etc. can be added.
*/
public final class Licenses extends AbstractHelp {
/** The report configuration to extract licenses from */
private final ReportConfiguration config;
/** The licenses in the report config */
private final SortedSet<ILicense> licenses;
/** The formatter */
private final HelpFormatter formatter;
/** The writer to write to */
private final PrintWriter printWriter;
/**
* Constructor
* @param config The configuration that contains the license information.
* @param writer the writer to write the report to.
*/
public Licenses(final ReportConfiguration config, final Writer writer) {
this.config = config;
this.licenses = config.getLicenses(LicenseFilter.ALL);
printWriter = new PrintWriter(writer);
formatter = HelpFormatter.builder().setShowDeprecated(false).setPrintWriter(printWriter).get();
}
/**
* Prints the text indented and wrapped.
* @param indent the number of spaces to indent.
* @param text the text to write.
*/
void print(final int indent, final String text) {
int leftMargin = indent * HELP_PADDING;
int tabStop = leftMargin + (HELP_PADDING / 2);
formatter.printWrapped(printWriter, HELP_WIDTH, tabStop, createPadding(leftMargin) + text);
}
/**
* Print the help text with the version information.
* @throws IOException on output error.
*/
public void printHelp() throws IOException {
print(0, format("Listing of licenses for %s", versionInfo));
output();
}
/**
* Output the License information without the version information.
* @throws IOException on error.
*/
public void output() throws IOException {
print(0, header("LICENSES"));
if (licenses.isEmpty()) {
print(0, "No licenses defined");
} else {
Description licenseDescription = DescriptionBuilder.build(licenses.first());
Collection<Description> licenseParams = licenseDescription.filterChildren(d -> d.getType() == ComponentType.PARAMETER);
print(0, format("Licenses have the following properties:%n"));
for (Description param : licenseParams) {
print(1, format("%s: %s%n", param.getCommonName(), param.getDescription()));
}
print(0, format("%nThe defined licenses are:%n"));
for (ILicense l : licenses) {
print(0, System.lineSeparator());
printObject(0, l);
}
}
print(0, header("DEFINED MATCHERS"));
SortedSet<Description> matchers = new TreeSet<>(Comparator.comparing(Description::getCommonName));
for (Class<? extends AbstractBuilder> mClazz : MatcherBuilderTracker.instance().getClasses()) {
try {
AbstractBuilder builder = mClazz.getConstructor().newInstance();
matchers.add(builder.getDescription());
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
throw new ConfigurationException(
format("Can not instantiate matcher builder %s ", mClazz.getName()), e);
}
}
for (Description description : matchers) {
print(0, format("%n%s: %s%n", description.getCommonName(), description.getDescription()));
for (Description child : description.getChildren().values()) {
if (IHeaderMatcher.class.isAssignableFrom(child.getChildType())) {
if (child.isCollection()) {
print(2, "Encloses multiple matchers");
} else {
print(2, "Wraps a single matcher");
}
} else {
print(1, format("%s: %s%s", child.getCommonName(), child.isRequired() ? "(required) " : "", child.getDescription()));
}
}
}
print(0, header("DEFINED FAMILIES"));
for (ILicenseFamily family : config.getLicenseFamilies(LicenseFilter.ALL)) {
print(1, format("%s - %s%n", family.getFamilyCategory(), family.getFamilyName()));
}
printWriter.flush();
}
/**
* Print the description of an object.
* @param indent the number of spaces to indent the print.
* @param object the object to print.
* @throws IOException on output error.
*/
private void printObject(final int indent, final Object object) throws IOException {
if (object == null) {
return;
}
Description description = DescriptionBuilder.build(object);
if (description == null) {
print(indent, format("Unknown Object of class: %s%n", object.getClass().getName()));
} else {
print(indent, format("%s (%s)%n", description.getCommonName(), description.getDescription()));
printChildren(indent + 1, object, description.getChildren());
}
}
/**
* Returns {@code true} if the string is a UUID.
* @param s the string to check.
* @return {@code true} if the string is a UUID.
*/
private boolean isUUID(final String s) {
try {
UUID.fromString(s);
return true;
} catch (IllegalArgumentException expected) {
return false;
}
}
/**
* Print the information for the children.
* @param indent the number of spaces to indent.
* @param parent the parent object.
* @param children the children of the parent.
* @throws IOException on write error.
*/
private void printChildren(final int indent, final Object parent, final Map<String, Description> children) throws IOException {
for (Description d : children.values()) {
switch (d.getType()) {
case PARAMETER:
if (d.isCollection()) {
print(indent, format("%s: %n", d.getCommonName()));
try {
Collection<?> result = (Collection<?>) d.getter(parent.getClass()).invoke(parent);
for (Object o : result) {
printObject(indent + 1, o);
}
return;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
if (IHeaderMatcher.class.isAssignableFrom(d.getChildType())) {
print(indent, format("%s: %n", d.getCommonName()));
// is a matcher.
try {
Object matcher = d.getter(parent.getClass()).invoke(parent);
printObject(indent + 1, matcher);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
String txt = StringUtils.defaultIfBlank(d.getParamValue(parent), "").replaceAll("\\s{2,}", " ");
if (!txt.isEmpty() && !(d.getCommonName().equals("id") && isUUID(txt))) {
print(indent, format("%s: %s%n", d.getCommonName(), txt.replaceAll("\\s{2,}", " ")));
}
}
break;
case BUILD_PARAMETER:
case LICENSE:
// do nothing
break;
case MATCHER:
printChildren(indent + 1, parent, d.getChildren());
break;
}
}
}
}