All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.is24.deadcode4j.analyzer.XmlAnalyzer Maven / Gradle / Ivy

package de.is24.deadcode4j.analyzer;

import de.is24.deadcode4j.Analyzer;
import de.is24.deadcode4j.CodeContext;
import org.apache.commons.io.IOUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;

/**
 * Serves as a base class with which to analyze XML files.
 *
 * @since 1.2.0
 */
public abstract class XmlAnalyzer implements Analyzer {
    private static final String NO_ATTRIBUTE = "no attribute";
    private final SAXParser parser;
    private final XmlHandler handler;
    private final String dependerId;
    private final String endOfFileName;
    private final String rootElement;
    private final Map relevantElements = newHashMap();
    private final Collection referencedClasses = newArrayList();

    /**
     * The constructor for an XmlAnalyzer.
     * 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, java.util.Collection)}
     * @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 XmlAnalyzer(@Nonnull String dependerId, @Nonnull String endOfFileName, @Nullable String rootElement) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            factory.setFeature("http://xml.org/sax/features/namespaces", true);
            this.parser = factory.newSAXParser();
        } catch (Exception e) {
            throw new RuntimeException("Failed to set up XML parser!", e);
        }
        if (endOfFileName.trim().length() == 0) {
            throw new IllegalArgumentException("[endOfFileName] must be set!");
        }
        this.handler = new XmlHandler();
        this.dependerId = dependerId;
        this.endOfFileName = endOfFileName;
        this.rootElement = rootElement;
    }

    @Override
    public final void doAnalysis(@Nonnull CodeContext codeContext, @Nonnull File file) {
        if (file.getName().endsWith(endOfFileName)) {
            analyzeXmlFile(codeContext, file);
        }
    }

    /**
     * Registers an XML element containing a fully qualified class name.
     *
     * @param elementName the name of the XML element to register
     */
    protected void registerClassElement(@Nonnull String elementName) {
        this.relevantElements.put(elementName, NO_ATTRIBUTE);
    }

    /**
     * Registers an XML element's attribute denoting a fully qualified class name.
     *
     * @param elementName   the name of the XML element the attribute may occur at
     * @param attributeName the name of the attribute to register
     */
    protected void registerClassAttribute(@Nonnull String elementName, @Nonnull String attributeName) {
        this.relevantElements.put(elementName, attributeName);
    }

    private void analyzeXmlFile(@Nonnull CodeContext codeContext, @Nonnull File file) {
        this.referencedClasses.clear();
        this.handler.reset();
        InputStream in = null;
        try {
            in = new FileInputStream(file);
            parser.parse(in, handler);
        } catch (StopParsing command) {
            return;
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse [" + file + "]!", e);
        } finally {
            IOUtils.closeQuietly(in);
        }
        codeContext.addDependencies(dependerId, this.referencedClasses);
    }

    /**
     * Used to indicate that XML parsing can be stopped.
     *
     * @since 1.2.0
     */
    private static class StopParsing extends SAXException {
    }

    /**
     * Used to indicate that XML parsing can be stopped.
     *
     * @since 1.2.0
     */
    private class XmlHandler extends DefaultHandler {
        private boolean firstElement = true;
        private StringBuilder buffer;

        @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;
            }
            String attributeName = relevantElements.get(localName);
            if (attributeName != null) {
                if (NO_ATTRIBUTE.equals(attributeName)) {
                    buffer = new StringBuilder(128);
                } else {
                    String className = attributes.getValue(attributeName);
                    if (className != null) {
                        referencedClasses.add(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) {
                referencedClasses.add(buffer.toString());
                buffer = null;
            }
        }

        public void reset() {
            this.firstElement = true;
            this.buffer = null;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy