XMLConfigurationWriter.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.configuration;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.UUID;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.rat.ImplementationException;
import org.apache.rat.ReportConfiguration;
import org.apache.rat.analysis.IHeaderMatcher;
import org.apache.rat.api.RatException;
import org.apache.rat.config.parameters.ComponentType;
import org.apache.rat.config.parameters.Description;
import org.apache.rat.configuration.builders.MatcherRefBuilder;
import org.apache.rat.license.ILicense;
import org.apache.rat.license.ILicenseFamily;
import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
import org.apache.rat.report.xml.writer.IXmlWriter;
import org.apache.rat.report.xml.writer.XmlWriter;
/**
* Writes the XML configuration file format.
*/
public class XMLConfigurationWriter {
/** The configuration that is being written */
private final ReportConfiguration configuration;
/** The set of defined matcher IDs */
private final Set<String> matchers;
/** The set of defined license IDs */
private final Set<String> licenseChildren;
/**
* Constructor
* @param configuration the configuration information to write.
*/
public XMLConfigurationWriter(final ReportConfiguration configuration) {
this.configuration = configuration;
this.matchers = new HashSet<>();
licenseChildren = new HashSet<>(Arrays.asList(XMLConfig.LICENSE_CHILDREN));
}
private Predicate<Description> attributeFilter(final Description parent) {
return d -> {
if (d.getType() == ComponentType.PARAMETER) {
return switch (parent.getType()) {
case MATCHER -> !XMLConfig.isInlineNode(parent.getCommonName(), d.getCommonName());
case LICENSE -> !licenseChildren.contains(d.getCommonName());
default -> true;
};
}
return false;
};
}
/**
* Writes the configuration to the specified writer.
* @param plainWriter a writer to write the XML to.
* @throws RatException on error.
*/
public void write(final Writer plainWriter) throws RatException {
write(new XmlWriter(plainWriter));
}
/**
* Writes the configuration to an IXmlWriter instance.
* @param writer the IXmlWriter to write to.
* @throws RatException on error.
*/
public void write(final IXmlWriter writer) throws RatException {
if (configuration.listFamilies() != LicenseFilter.NONE || configuration.listLicenses() != LicenseFilter.NONE) {
try {
writer.openElement(XMLConfig.ROOT);
// Families section
SortedSet<ILicenseFamily> families = configuration.getLicenseFamilies(configuration.listFamilies());
if (!families.isEmpty()) {
writer.openElement(XMLConfig.FAMILIES);
for (ILicenseFamily family : families) {
writeFamily(writer, family);
}
writer.closeElement(); // FAMILIES
}
// licenses section
SortedSet<ILicense> licenses = configuration.getLicenses(configuration.listLicenses());
if (!licenses.isEmpty()) {
writer.openElement(XMLConfig.LICENSES);
for (ILicense license : licenses) {
writeDescription(writer, license.getDescription(), license);
}
writer.closeElement(); // LICENSES
}
// approved section
writer.openElement(XMLConfig.APPROVED);
for (String family : configuration.getLicenseCategories(LicenseFilter.APPROVED)) {
writer.openElement(XMLConfig.APPROVED).attribute(XMLConfig.ATT_LICENSE_REF, family.trim())
.closeElement();
}
writer.closeElement(); // APPROVED
// matchers section
MatcherBuilderTracker tracker = MatcherBuilderTracker.instance();
writer.openElement(XMLConfig.MATCHERS);
for (Class<?> clazz : tracker.getClasses()) {
writer.openElement(XMLConfig.MATCHER).attribute(XMLConfig.ATT_CLASS_NAME, clazz.getCanonicalName())
.closeElement();
}
writer.closeElement(); // MATCHERS
writer.closeElement(); // ROOT
} catch (IOException e) {
throw new RatException(e);
}
}
}
private void writeFamily(final IXmlWriter writer, final ILicenseFamily family) throws RatException {
try {
writer.openElement(XMLConfig.FAMILY).attribute(XMLConfig.ATT_ID, family.getFamilyCategory().trim())
.attribute(XMLConfig.ATT_NAME, family.getFamilyName());
writer.closeElement();
} catch (IOException e) {
throw new RatException(e);
}
}
private void writeDescriptions(final IXmlWriter writer, final Collection<Description> descriptions, final IHeaderMatcher component)
throws RatException {
for (Description description : descriptions) {
writeDescription(writer, description, component);
}
}
private void writeChildren(final IXmlWriter writer, final Description description, final IHeaderMatcher component)
throws RatException {
writeAttributes(writer, description.filterChildren(attributeFilter(component.getDescription())), component);
writeDescriptions(writer, description.filterChildren(attributeFilter(component.getDescription()).negate()),
component);
}
private void writeAttributes(final IXmlWriter writer, final Collection<Description> descriptions, final IHeaderMatcher component)
throws RatException {
for (Description d : descriptions) {
try {
writeAttribute(writer, d, component);
} catch (IOException e) {
throw new RatException(e);
}
}
}
private void writeComment(final IXmlWriter writer, final Description description) throws IOException {
if (StringUtils.isNotBlank(description.getDescription())) {
writer.comment(description.getDescription().replace("-->", "-–>"));
}
}
private void writeAttribute(final IXmlWriter writer, final Description description, final IHeaderMatcher component)
throws IOException {
String paramValue = description.getParamValue(component);
if (paramValue != null) {
writer.attribute(description.getCommonName(), paramValue);
}
}
/* package private for testing */
@SuppressWarnings("unchecked")
void writeDescription(final IXmlWriter writer, final Description desc, final IHeaderMatcher comp) throws RatException {
Description description = desc;
IHeaderMatcher component = comp;
try {
switch (description.getType()) {
case MATCHER:
// see if id was registered
Optional<Description> id = description.childrenOfType(ComponentType.PARAMETER).stream()
.filter(i -> XMLConfig.ATT_ID.equals(i.getCommonName())).findFirst();
// id will not be present in matcherRef
if (id.isPresent()) {
String matcherId = id.get().getParamValue(component);
// if we have seen the ID before, put a reference to the other one.
if (matchers.contains(matcherId)) {
component = new MatcherRefBuilder.IHeaderMatcherProxy(matcherId, null);
description = component.getDescription();
} else {
matchers.add(matcherId);
}
// remove the matcher id if it is a UUID
try {
UUID.fromString(matcherId);
description.getChildren().remove(XMLConfig.ATT_ID);
} catch (IllegalArgumentException expected) {
if (description.getCommonName().equals("spdx")) {
description.getChildren().remove(XMLConfig.ATT_ID);
}
}
}
// if only a resource, list the resource not the contents of the matcher
Optional<Description> resource = description.childrenOfType(ComponentType.PARAMETER).stream()
.filter(i -> XMLConfig.ATT_RESOURCE.equals(i.getCommonName())).findFirst();
if (resource.isPresent()) {
String resourceStr = resource.get().getParamValue(component);
if (StringUtils.isNotBlank(resourceStr)) {
description.getChildren().remove("enclosed");
}
}
writeComment(writer, description);
writer.openElement(description.getCommonName());
writeChildren(writer, description, component);
writer.closeElement();
break;
case LICENSE:
writer.openElement(XMLConfig.LICENSE);
writeChildren(writer, description, component);
writer.closeElement();
break;
case PARAMETER:
if ("id".equals(description.getCommonName())) {
try {
String paramId = description.getParamValue(component);
// if a UUID skip it.
if (paramId != null) {
UUID.fromString(paramId);
return;
}
} catch (IllegalArgumentException expected) {
// do nothing.
}
}
if (description.getChildType() == String.class) {
boolean inline = XMLConfig.isInlineNode(component.getDescription().getCommonName(),
description.getCommonName());
String s = description.getParamValue(component);
if (StringUtils.isNotBlank(s)) {
if (!inline) {
writer.openElement(description.getCommonName());
}
writer.content(description.getParamValue(component));
if (!inline) {
writer.closeElement();
}
}
} else {
try {
if (description.isCollection()) {
for (IHeaderMatcher matcher : (Collection<IHeaderMatcher>) description
.getter(component.getClass()).invoke(component)) {
writeDescription(writer, matcher.getDescription(), matcher);
}
} else {
IHeaderMatcher matcher = (IHeaderMatcher) description.getter(component.getClass())
.invoke(component);
writeDescription(writer, matcher.getDescription(), matcher);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException | RatException e) {
throw new ImplementationException(e);
}
}
break;
case BUILD_PARAMETER:
// ignore;
break;
}
} catch (IOException e) {
throw new RatException(e);
}
}
}