package de.is24.deadcode4j.analyzer.webxml; import de.is24.deadcode4j.AnalysisContext; import de.is24.deadcode4j.analyzer.XmlAnalyzer; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; import javax.annotation.Nonnull; import java.util.*; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.collect.Iterables.elementsEqual; import static java.util.Arrays.asList; /** * Parses {@code web.xml} and translates XML events into {@code web.xml} * specific events that can be consumed by a {@link WebXmlHandler}. It only * creates events for {@code web.xml} nodes that are needed by existing * {@link de.is24.deadcode4j.Analyzer}s. Please add further events if needed. * * @since 2.1.0 */ public abstract class BaseWebXmlAnalyzer extends XmlAnalyzer { protected BaseWebXmlAnalyzer() { super("web.xml"); } /** * This method is called to provide a <code>WebXmlHandler</code> for each file being processed. */ @Nonnull protected abstract WebXmlHandler createWebXmlHandlerFor(@Nonnull AnalysisContext analysisContext); @Nonnull @Override protected DefaultHandler createHandlerFor(@Nonnull AnalysisContext analysisContext) { WebXmlHandler webXmlHandler = createWebXmlHandlerFor(analysisContext); return new WebXmlAdapter(webXmlHandler); } // Translates XML events into web.xml events that can be consumed by a WebXmlHandler private static class WebXmlAdapter extends DefaultHandler { @SuppressWarnings("unchecked") static final Collection<List<String>> NODES_WITH_TEXT = asList( asList("web-app", "context-param", "param-name"), asList("web-app", "context-param", "param-value"), asList("web-app", "filter", "filter-class"), asList("web-app", "filter", "init-param", "param-name"), asList("web-app", "filter", "init-param", "param-value"), asList("web-app", "listener", "listener-class"), asList("web-app", "servlet", "servlet-class"), asList("web-app", "servlet", "init-param", "param-name"), asList("web-app", "servlet", "init-param", "param-value")); static final Collection<String> CONTEXT_PARAM_PATH = asList("web-app", "context-param"); static final Collection<String> FILTER_PATH = asList("web-app", "filter"); static final Collection<String> FILTER_INIT_PARAM_PATH = asList("web-app", "filter", "init-param"); static final Collection<String> LISTENER_PATH = asList("web-app", "listener"); static final Collection<String> SERVLET_INIT_PARAM_PATH = asList("web-app", "servlet", "init-param"); static final Collection<String> SERVLET_PATH = asList("web-app", "servlet"); final Deque<String> deque = new ArrayDeque<String>(); final List<Param> initParams = new ArrayList<Param>(); final Map<String, String> texts = new HashMap<String, String>(); final Deque<StringBuilder> textBuffers = new ArrayDeque<StringBuilder>(); final WebXmlHandler webXmlHandler; WebXmlAdapter(WebXmlHandler webXmlHandler) { this.webXmlHandler = webXmlHandler; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { deque.add(localName); if (isNodeWithText()) { textBuffers.addLast(new StringBuilder(128)); } } @Override public void characters(char[] ch, int start, int length) { if (isNodeWithText()) { textBuffers.getLast().append(new String(ch, start, length).trim()); } } @Override public void endElement(String uri, String localName, String qName) { if (isNodeWithText()) { storeCharacters(localName); } else if (matchesPath(CONTEXT_PARAM_PATH)) { reportContextParam(); } else if (matchesPath(FILTER_INIT_PARAM_PATH)) { storeInitParam(); } else if (matchesPath(FILTER_PATH)) { reportFilter(); } else if (matchesPath(LISTENER_PATH)) { reportListener(); } else if (matchesPath(SERVLET_INIT_PARAM_PATH)) { storeInitParam(); } else if (matchesPath(SERVLET_PATH)) { reportServlet(); } deque.removeLast(); } boolean isNodeWithText() { for (List<String> candidate : NODES_WITH_TEXT) { if (matchesPath(candidate)) { return true; } } return false; } void reportContextParam() { webXmlHandler.contextParam(createParam()); } void storeInitParam() { initParams.add(createParam()); } Param createParam() { return new Param(getText("param-name"), getText("param-value")); } void reportFilter() { webXmlHandler.filter( getText("filter-class"), new ArrayList<Param>(initParams)); initParams.clear(); } void reportListener() { webXmlHandler.listener(getText("listener-class")); } void reportServlet() { webXmlHandler.servlet( getText("servlet-class"), new ArrayList<Param>(initParams)); initParams.clear(); } boolean matchesPath(Collection<String> path) { return path.size() == deque.size() && elementsEqual(path, deque); } void storeCharacters(String localName) { texts.put(localName, textBuffers.removeLast().toString()); } String getText(String localName) { String text = texts.remove(localName); return nullToEmpty(text); } } }