package de.is24.deadcode4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import static com.google.common.collect.Maps.newHashMap; import static com.google.common.collect.Sets.newHashSet; /** * Instances of <code>IntermediateResults</code> are used to keep track of and calculate the {@link IntermediateResult}s * produced by and being made available to {@link AnalysisContext} instances, respectively. * * @since 2.0.0 */ public final class IntermediateResults { @Nonnull private final Logger logger = LoggerFactory.getLogger(getClass()); @Nonnull private final Map<Module, Map<Object, IntermediateResult>> intermediateResults = newHashMap(); /** * Returns an <code>IntermediateResultSet</code> for the given <code>Set</code>.<br/> * This method is defined for type inference, as it could simply be replaced with a constructor call. * * @since 2.0.0 */ @Nonnull public static <E> IntermediateResultSet<E> resultSetFor(@Nonnull Collection<E> intermediateResults) { return new IntermediateResultSet<E>(intermediateResults); } /** * Returns an <code>IntermediateResultMap</code> for the given <code>Map</code>.<br/> * This method is defined for type inference, as it could simply be replaced with a constructor call. * * @since 2.0.0 */ @Nonnull public static <K, V> IntermediateResultMap<K, V> resultMapFor(@Nonnull Map<K, V> intermediateResults) { return new IntermediateResultMap<K, V>(intermediateResults); } /** * Returns an <code>IntermediateResultSet</code> from the given <code>AnalysisContext</code> for the given key.<br/> * This method is defined to handle the <i>unchecked</i> cast to a typed <code>IntermediateResultSet</code>, * it could simply be replaced with {@link AnalysisContext#getIntermediateResult(Object)}. * * @since 2.0.0 */ @Nullable @SuppressWarnings("unchecked") public static <E> IntermediateResultSet<E> resultSetFrom(@Nonnull AnalysisContext analysisContext, Object key) { return (IntermediateResultSet<E>) analysisContext.getIntermediateResult(key); } /** * Returns an <code>IntermediateResultMap</code> from the given <code>AnalysisContext</code> for the given key.<br/> * This method is defined to handle the <i>unchecked</i> cast to a typed <code>IntermediateResultMap</code>, * it could simply be replaced with {@link AnalysisContext#getIntermediateResult(Object)}. * * @since 2.0.0 */ @Nullable @SuppressWarnings("unchecked") public static <K, V> IntermediateResultMap<K, V> resultMapFrom(@Nonnull AnalysisContext analysisContext, Object key) { return (IntermediateResultMap<K, V>) analysisContext.getIntermediateResult(key); } /** * Adds the intermediate results of the given analysis context's cache. * * @since 2.0.0 */ public void add(@Nonnull AnalysisContext analysisContext) { intermediateResults.put(analysisContext.getModule(), getIntermediateResultsOf(analysisContext)); } /** * Calculates the intermediate results being available for the specified module. * * @since 2.0.0 */ @Nonnull public Map<Object, IntermediateResult> calculateIntermediateResultsFor(@Nonnull Module module) { return calculateIntermediateResults(module); } @Nonnull private Map<Object, IntermediateResult> getIntermediateResultsOf(@Nonnull AnalysisContext analysisContext) { Map<Object, IntermediateResult> intermediateResults = newHashMap(); for (Map.Entry<Object, Object> cachedEntry : analysisContext.getCache().entrySet()) { Object cachedValue = cachedEntry.getValue(); if (IntermediateResult.class.isInstance(cachedValue)) { logger.debug("{} stored [{}].", analysisContext.getModule(), cachedValue); intermediateResults.put(cachedEntry.getKey(), IntermediateResult.class.cast(cachedValue)); } } return intermediateResults; } @Nonnull private Map<Object, IntermediateResult> calculateIntermediateResults(@Nonnull Module module) { Map<Object, IntermediateResult> results = calculateResultsOfParentsFor(module); mergeWithResultsOf(module, results); return results; } @Nonnull private Map<Object, IntermediateResult> calculateResultsOfParentsFor(@Nonnull Module module) { Map<Object, IntermediateResult> mergedResults = newHashMap(); for (Module requiredModule : module.getRequiredModules()) { Map<Object, IntermediateResult> results = calculateIntermediateResults(requiredModule); for (Map.Entry<Object, IntermediateResult> resultEntry : results.entrySet()) { Object key = resultEntry.getKey(); IntermediateResult intermediateResult = resultEntry.getValue(); IntermediateResult existingResult = mergedResults.get(key); mergedResults.put(key, existingResult == null ? intermediateResult : existingResult.mergeSibling(intermediateResult) ); } } return mergedResults; } private void mergeWithResultsOf(@Nonnull Module module, @Nonnull Map<Object, IntermediateResult> combinedResults) { Map<Object, IntermediateResult> intermediateResultsOfModule = intermediateResults.get(module); if (intermediateResultsOfModule == null) { return; } for (Map.Entry<Object, IntermediateResult> resultEntry : intermediateResultsOfModule.entrySet()) { Object key = resultEntry.getKey(); IntermediateResult intermediateResult = resultEntry.getValue(); IntermediateResult parentResult = combinedResults.get(key); combinedResults.put(key, parentResult == null ? intermediateResult : intermediateResult.mergeParent(parentResult) ); } } /** * An <code>IntermediateResultSet</code> is an implementation of {@link de.is24.deadcode4j.IntermediateResult} using * a <code>Set</code> to store the results. Concerning merging with siblings & parents, it simply adds both sets. * * @since 2.0.0 */ public static class IntermediateResultSet<E> implements IntermediateResult { @Nonnull private final Set<E> results; /** * Creates an <code>IntermediateResultSet</code> to store the given <code>Set</code>. * * @since 2.0.0 */ public IntermediateResultSet(@Nonnull Collection<E> results) { this.results = Collections.unmodifiableSet(newHashSet(results)); } @Override public String toString() { return getClass().getSimpleName() + ": " + results; } @Nonnull @Override public IntermediateResult mergeSibling(@Nonnull IntermediateResult sibling) { return merge(sibling); } @Nonnull @Override public IntermediateResult mergeParent(@Nonnull IntermediateResult parent) { return merge(parent); } /** * Returns the stored read-only <code>Set</code>. * * @since 2.0.0 */ @Nonnull public Set<E> getResults() { return this.results; } @Nonnull private IntermediateResult merge(@Nonnull IntermediateResult result) { Set<E> mergedResults = newHashSet(this.results); mergedResults.addAll(getResults(result)); return new IntermediateResultSet<E>(mergedResults); } @Nonnull @SuppressWarnings("unchecked") private Set<E> getResults(@Nonnull IntermediateResult result) { return IntermediateResultSet.class.cast(result).getResults(); } } /** * An <code>IntermediateResultMap</code> is an implementation of {@link de.is24.deadcode4j.IntermediateResult} using * a <code>Map</code> to store the results. Concerning merging with siblings & parents, it * <ul> * <li>adds entries of siblings & parents to the results if they don't collide with those defined by itself</li> * <li>if an entry of sibling or parent collides with one defined by itself * <ul> * <li>and the values are <code>Collection</code>s, they are added to one another</li> * <li>otherwise, the own value is kept and the other is discarded</li> * </ul> * </li> * </ul> * * @since 2.0.0 */ public static class IntermediateResultMap<K, V> implements IntermediateResult { @Nonnull private final Logger logger = LoggerFactory.getLogger(getClass()); @Nonnull private final Map<K, V> results; /** * Creates an <code>IntermediateResultMap</code> to store the given <code>Map</code>. * * @since 2.0.0 */ public IntermediateResultMap(@Nonnull Map<K, V> results) { this.results = Collections.unmodifiableMap(newHashMap(results)); } @Nonnull @Override public String toString() { return getClass().getSimpleName() + ": " + this.results; } @Nonnull @Override public IntermediateResult mergeSibling(@Nonnull IntermediateResult sibling) { return merge(sibling); } @Nonnull @Override public IntermediateResult mergeParent(@Nonnull IntermediateResult parent) { return merge(parent); } /** * Returns the stored read-only <code>Map</code>. * * @since 2.0.0 */ @Nonnull public Map<K, V> getResults() { return this.results; } @Nonnull @SuppressWarnings("unchecked") private IntermediateResult merge(@Nonnull IntermediateResult result) { Map<K, V> mergedResults = newHashMap(getResults()); for (Map.Entry<K, V> resultEntry : getResults(result).entrySet()) { K key = resultEntry.getKey(); V value = resultEntry.getValue(); V existingResult = mergedResults.get(key); if (existingResult == null) { mergedResults.put(key, value); } else if (Collection.class.isInstance(existingResult)) { Collection.class.cast(existingResult).addAll(Collection.class.cast(value)); } else if (!existingResult.equals(value)) { logger.debug("Intermediate result [{}] refers to [{}] and [{}] defined by different modules, keeping the former.", key, existingResult, value); } } return new IntermediateResultMap<K, V>(mergedResults); } @Nonnull @SuppressWarnings("unchecked") private Map<K, V> getResults(IntermediateResult result) { return IntermediateResultMap.class.cast(result).getResults(); } } }