package sk.stuba.fiit.perconik.activity.listeners.java.dom; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import static com.google.common.base.Optional.absent; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkNotNull; import static sk.stuba.fiit.perconik.eclipse.jdt.core.JavaElementDeltaFlag.AST_AFFECTED; import static sk.stuba.fiit.perconik.eclipse.jdt.core.JavaElementDeltaFlag.setOf; abstract class AbstractCachingNodeListener<T, N extends ASTNode> extends AbstractJavaElementListener { final Function<? super CompilationUnit, ? extends Iterable<N>> collector; final Function<? super N, ? extends Optional<T>> keyer; final KeyerFailureBehavior behavior; final Cache<T, N> cache; AbstractCachingNodeListener(final CachingConfiguration<T, N> configuration) { this.collector = checkNotNull(configuration.collector); this.keyer = checkNotNull(configuration.keyer); this.behavior = checkNotNull(configuration.behavior); this.cache = configuration.build(); } static class CachingConfiguration<T, N extends ASTNode> { Function<? super CompilationUnit, ? extends Iterable<N>> collector; Function<? super N, ? extends Optional<T>> keyer; KeyerFailureBehavior behavior; CacheBuilder<Object, Object> builder; CacheLoader<? super T, N> loader; CachingConfiguration() {} final void collector(final Function<? super CompilationUnit, ? extends Iterable<N>> collector) { this.collector = checkNotNull(collector); } final void keyer(final Function<? super N, ? extends Optional<T>> keyer) { this.keyer = checkNotNull(keyer); } final void behavior(final KeyerFailureBehavior behavior) { this.behavior = checkNotNull(behavior); } final void builder(final CacheBuilder<Object, Object> builder) { this.builder = checkNotNull(builder); } final void loader(final CacheLoader<? super T, N> loader) { this.loader = checkNotNull(loader); } final Cache<T, N> build() { if (this.loader == null) { return this.builder.build(); } return this.builder.build(this.loader); } } enum KeyerFailureBehavior { REJECT { @Override <T, N extends ASTNode> Optional<? extends T> handle(final Function<? super N, ? extends Optional<T>> keyer, final N revised) { try { return checkNotNull(keyer.apply(revised)); } catch (Exception ignore) { return absent(); } } }, RAISE { @Override <T, N extends ASTNode> Optional<? extends T> handle(final Function<? super N, ? extends Optional<T>> keyer, final N revised) { try { return checkNotNull(keyer.apply(revised)); } catch (Exception failure) { throw new KeyComputationException(failure); } } }; abstract <T, N extends ASTNode> Optional<? extends T> handle(Function<? super N, ? extends Optional<T>> keyer, N revised); } static class KeyComputationException extends RuntimeException { private static final long serialVersionUID = 0L; KeyComputationException(final Throwable cause) { super(cause); } } private Iterable<N> collect(final IJavaElementDelta delta) { return this.collector.apply(delta.getCompilationUnitAST()); } private Optional<? extends T> key(final N revised) { return this.behavior.handle(this.keyer, revised); } private N update(final T key, final N revised) { return this.cache.asMap().put(key, revised); } @Override final void process(final long time, final ElementChangedEvent event) { IJavaElementDelta delta = event.getDelta(); if (!setOf(delta.getFlags()).contains(AST_AFFECTED)) { return; } for (N revised: this.collect(delta)) { Optional<? extends T> option = this.key(revised); if (option.isPresent()) { T key = option.get(); N original = this.update(key, revised); this.process(time, event, key, fromNullable(original), revised); } } } abstract void process(long time, ElementChangedEvent event, T key, Optional<N> original, N revised); }