package de.is24.deadcode4j.analyzer; import com.google.common.collect.Maps; import de.is24.deadcode4j.AnalysisContext; import de.is24.deadcode4j.Analyzer; import de.is24.deadcode4j.IntermediateResult; import de.is24.deadcode4j.Module; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; import java.util.Map; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.isEmpty; /** * Analyzes both <code>web.xml</code> and class files: looks for implementations of * {@link javax.servlet.ServletContainerInitializer} if the <code>metadata-complete</code> attribute of the * <code>web-app</code> element is missing or set to "false". * * @since 1.5 */ public class ServletContainerInitializerAnalyzer extends AnalyzerAdapter { private final String depender; private final Analyzer classFinder; private final Analyzer webXmlAnalyzer = new XmlAnalyzer("web.xml") { @Nonnull @Override protected DefaultHandler createHandlerFor(@Nonnull final AnalysisContext analysisContext) { return new DefaultHandler() { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws StopParsing { if ("web-app".equals(localName) && "true".equals(attributes.getValue("metadata-complete"))) { ((ServletContainerInitializerAnalysisContext) analysisContext).setMetadataComplete(); } throw new StopParsing(); } }; } }; private ServletContainerInitializerAnalysisContext context; /** * Creates a new instance of <code>ServletContainerInitializerAnalyzer</code>. * * @param dependerId a description of the <i>depending entity</i> with which to * call {@link de.is24.deadcode4j.AnalysisContext#addDependencies(String, Iterable)} * @param fqcnOfInitializerInterface the fqcn of the interface whose implementations represent a * <code>ServletContainerInitializer</code> or something comparable */ protected ServletContainerInitializerAnalyzer(String dependerId, String fqcnOfInitializerInterface) { this.depender = dependerId; this.classFinder = new InterfacesAnalyzer("ServletContainerInitializer-implementation", fqcnOfInitializerInterface) { }; } public ServletContainerInitializerAnalyzer() { this("JEE-ServletContainerInitializer", "javax.servlet.ServletContainerInitializer"); } @Override public void doAnalysis(@Nonnull AnalysisContext analysisContext, @Nonnull File fileName) { if (this.context == null) { this.context = new ServletContainerInitializerAnalysisContext(analysisContext.getModule()); this.context.setOriginalContext(analysisContext); } this.webXmlAnalyzer.doAnalysis(this.context, fileName); this.classFinder.doAnalysis(this.context, fileName); } @Override public void finishAnalysis(@Nonnull AnalysisContext analysisContext) { ServletContainerInitializerAnalysisContext localContext = this.context; this.context = null; if (localContext == null) { return; } if (localContext.isMetadataComplete()) { logger.debug("Found web.xml with completed metadata; " + "ServletContainerInitializer implementations are treated as dead code"); return; } Iterable<String> initializerClasses = concat(localContext.getAnalyzedCode().getCodeDependencies().values()); if (!isEmpty(initializerClasses)) { analysisContext.addDependencies(depender, initializerClasses); } } private static class ServletContainerInitializerAnalysisContext extends AnalysisContext { private AnalysisContext originalContext; private boolean metadataComplete = false; ServletContainerInitializerAnalysisContext(Module module) { super(module, Maps.<Object, IntermediateResult>newHashMap()); } @Nonnull @Override public Map<Object, Object> getCache() { return this.originalContext.getCache(); } @Nullable @Override public IntermediateResult getIntermediateResult(@Nonnull Object key) { return this.originalContext.getIntermediateResult(key); } @Override public void addAnalyzedClass(@Nonnull String clazz) { this.originalContext.addAnalyzedClass(clazz); } public void setMetadataComplete() { this.metadataComplete = true; } public boolean isMetadataComplete() { return metadataComplete; } public void setOriginalContext(AnalysisContext analysisContext) { this.originalContext = analysisContext; } } }