de.is24.deadcode4j.analyzer.SimpleXmlAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of deadcode4j-maven-plugin Show documentation
Show all versions of deadcode4j-maven-plugin Show documentation
Finds unused classes of a project
package de.is24.deadcode4j.analyzer;
import de.is24.deadcode4j.Analyzer;
import de.is24.deadcode4j.CodeContext;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
/**
* Serves as simple base class with which to analyze XML files by defining which element nodes' text or attributes
* contain a fqcn.
*
* @since 1.2.0
*/
public abstract class SimpleXmlAnalyzer extends XmlAnalyzer implements Analyzer {
private final String dependerId;
private final String rootElement;
private final Set registeredElements = newHashSet();
/**
* The constructor for a SimpleXmlAnalyzer
.
* Be sure to call {@link #registerClassElement(String)} and/or {@link #registerClassAttribute(String, String)} in the subclasses'
* constructor.
*
* @param dependerId a description of the depending entity with which to
* call {@link de.is24.deadcode4j.CodeContext#addDependencies(String, Iterable)}
* @param endOfFileName the file suffix used to determine if a file should be analyzed; this can be a mere file
* extension like .xml or a partial path like WEB-INF/web.xml
* @param rootElement the expected XML root element or null
if such an element does not exist;
* i.e. there are multiple valid root elements
* @since 1.2.0
*/
protected SimpleXmlAnalyzer(@Nonnull String dependerId, @Nonnull String endOfFileName, @Nullable String rootElement) {
super(endOfFileName);
this.dependerId = dependerId;
this.rootElement = rootElement;
}
@Override
@Nonnull
protected final DefaultHandler createHandlerFor(@Nonnull CodeContext codeContext) {
return new XmlHandler(codeContext);
}
/**
* Registers an XML element containing a fully qualified class name. The returned Element
can be
* {@link Element#withAttributeValue(String, String) restricted further}.
*
* @param elementName the name of the XML element to register
* @since 1.2.0
*/
protected Element registerClassElement(@Nonnull String elementName) {
Element element = new Element(elementName);
element.reportTextAsClass();
this.registeredElements.add(element);
return element;
}
/**
* Registers an XML element's attribute denoting a fully qualified class name. The returned Element
can
* be {@link Element#withAttributeValue(String, String) restricted further}.
*
* @param elementName the name of the XML element the attribute may occur at
* @param attributeName the name of the attribute to register
* @since 1.2.0
*/
protected Element registerClassAttribute(@Nonnull String elementName, @Nonnull String attributeName) {
Element element = new Element(elementName);
element.setAttributeToReportAsClass(attributeName);
this.registeredElements.add(element);
return element;
}
/**
* @since 1.2.0
*/
private class XmlHandler extends DefaultHandler {
private final CodeContext codeContext;
private boolean firstElement = true;
private StringBuilder buffer;
public XmlHandler(CodeContext codeContext) {
this.codeContext = codeContext;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws StopParsing {
if (firstElement && rootElement != null && !rootElement.equals(localName)) {
throw new StopParsing();
} else {
firstElement = false;
}
for (Element registeredElement : registeredElements) {
if (registeredElement.matches(localName, attributes)) {
if (registeredElement.shouldReportTextAsClass()) {
buffer = new StringBuilder(128);
}
String attributeToReportAsClass = registeredElement.getAttributeToReportAsClass();
if (attributeToReportAsClass != null) {
String className = attributes.getValue(attributeToReportAsClass);
if (className != null) {
codeContext.addDependencies(dependerId, className.trim());
}
}
}
}
}
@Override
public void characters(char[] ch, int start, int length) {
if (buffer != null) {
buffer.append(new String(ch, start, length).trim());
}
}
@Override
public void endElement(String uri, String localName, String qName) {
if (buffer != null && buffer.length() > 0) {
codeContext.addDependencies(dependerId, buffer.toString());
buffer = null;
}
}
}
/**
* Represents an XML element that is to be examined.
*
* @since 1.5
*/
protected static class Element {
private final String name;
private final Map requiredAttributeValues = newHashMap();
private boolean reportTextAsClass = false;
private String attributeToReportAsClass;
public Element(@Nonnull String name) {
checkArgument(name.trim().length() > 0, "The element's [name] must be set!");
this.name = name;
}
/**
* Restricts the element to only be examined if it has an attribute with the specified value.
*
* @since 1.5
*/
public Element withAttributeValue(@Nonnull String attributeName, @Nonnull String requiredValue) {
checkArgument(attributeName.trim().length() > 0, "[attributeName] must be given!");
checkArgument(requiredValue.trim().length() > 0, "[requiredValue] must be given!");
this.requiredAttributeValues.put(attributeName, requiredValue);
return this;
}
void reportTextAsClass() {
this.reportTextAsClass = true;
}
boolean shouldReportTextAsClass() {
return this.reportTextAsClass;
}
void setAttributeToReportAsClass(@Nonnull String attributeName) {
checkArgument(attributeName.trim().length() > 0, "[attributeName] must be given!");
checkState(this.attributeToReportAsClass == null,
"Already registered [" + this.attributeToReportAsClass + "] as attribute to report as class!");
this.attributeToReportAsClass = attributeName;
}
String getAttributeToReportAsClass() {
return this.attributeToReportAsClass;
}
boolean matches(String localName, Attributes attributes) {
if (!name.equals(localName)) {
return false;
}
for (Map.Entry entry : this.requiredAttributeValues.entrySet()) {
String expectedValue = entry.getValue();
String currentValue = attributes.getValue(entry.getKey());
if (!expectedValue.equals(currentValue)) {
return false;
}
}
return true;
}
}
}