package org.jbehave.eclipse.cache; import static org.jbehave.eclipse.util.Bytes.areDifferents; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaModelException; import org.jbehave.eclipse.cache.container.Container; import org.jbehave.eclipse.cache.container.HierarchicalContainer; import org.jbehave.eclipse.util.Visitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fj.Effect; public class MethodCache<E> extends JavaVisitorAdapter<MethodCache.Bucket<E>> { public interface Callback<T1, T2> { void op(T1 value1, T2 value2); } private static AtomicInteger buildTickGenerator = new AtomicInteger(); private Logger log = LoggerFactory.getLogger(MethodCache.class); private final HierarchicalContainer<E> content = new HierarchicalContainer<E>("<root>"); private Callback<IMethod, Container<E>> callback; private byte[] cachedFilterHash; public MethodCache(Callback<IMethod, Container<E>> callback) { this.callback = callback; } public void rebuild(IJavaProject project, Effect<JavaScanner<?>> initializer, Executor executor) throws JavaModelException { JavaScanner<Bucket<E>> javaScanner = new JavaScanner<Bucket<E>>(project, this, executor); initializer.e(javaScanner); int buildTick = buildTickGenerator.incrementAndGet(); byte[] filterHash = javaScanner.getFilterHash(); boolean filterChanged = hasFilterChanged(filterHash); Bucket<E> rootBucket = newRootBucket(buildTick, filterChanged); javaScanner.traversePackageFragmentRoots(rootBucket); removeUnsed(buildTick); } public void traverse(Visitor<E, ?> visitor) { content.traverse(visitor); } protected boolean hasFilterChanged(byte[] bytes) { if (bytes == null || cachedFilterHash == null || areDifferents(bytes, cachedFilterHash)) { cachedFilterHash = bytes; return true; } return false; } public Bucket<E> newRootBucket(int buildTick, boolean hasFilterHashChanged) { return new Bucket<E>(buildTick, hasFilterHashChanged, content); } protected void removeUnsed(final int buildTick) { log.debug("Removing unsed container (e.g. element deleted, moved or renamed). This should be done asynchronously."); Job job = new Job("Victor the method cleaner (build #" + buildTick + ")") { @Override protected IStatus run(IProgressMonitor monitor) { content.recursivelyRemoveBuildOlderThan(buildTick, monitor); return Status.OK_STATUS; } }; job.schedule(); // start as soon as possible } @Override public boolean visit(IPackageFragmentRoot packageFragmentRoot, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(packageFragmentRoot); if(bucket.hasFilterChanged()) { container.resetForBuild(packageFragmentRoot, bucket.buildTick); return true; } else { return container.prepareForTraversal(packageFragmentRoot, bucket.buildTick); } } @Override public Bucket<E> argumentFor(IPackageFragmentRoot packageFragmentRoot, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(packageFragmentRoot); return bucket.withContainer(container); } @Override public boolean visit(IPackageFragment packageFragment, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(packageFragment); if(bucket.hasFilterChanged()) { container.resetForBuild(packageFragment, bucket.buildTick); return true; } else { return container.prepareForTraversal(packageFragment, bucket.buildTick); } } @Override public Bucket<E> argumentFor(IPackageFragment packageFragment, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(packageFragment); return bucket.withContainer(container); } @Override public boolean traverseClassFile(IPackageFragment packageFragment, Bucket<E> bucket) { return true; } @Override public boolean traverseCompilationUnit(IPackageFragment packageFragment, Bucket<E> bucket) { return true; } @Override public boolean visit(ICompilationUnit cunit, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(cunit); if(bucket.hasFilterChanged()) { container.resetForBuild(cunit, bucket.buildTick); return true; } else { return container.prepareForTraversal(cunit, bucket.buildTick); } } @Override public Bucket<E> argumentFor(ICompilationUnit cunit, Bucket<E> bucket) { Container<E> container = bucket.container.specializeFor(cunit); return bucket.withContainer(container); } @Override public boolean visit(IMethod method, Bucket<E> bucket) { Container<E> container = bucket.getContainer(); callback.op(method, container); return false; } /** * Class is public in order to be invoked. But all methods are private for internal use. */ public static final class Bucket<E> { private final Container<E> container; private final boolean filterChanged; private final int buildTick; private Bucket(int buildTick, boolean filterChanged) { this(buildTick, filterChanged, null); } private Bucket(int buildTick, boolean filterChanged, Container<E> container) { this.buildTick = buildTick; this.filterChanged = filterChanged; this.container = container; } public boolean hasFilterChanged() { return filterChanged; } private Container<E> getContainer() { return container; } private Bucket<E> withContainer(Container<E> container) { if (this.container == container) return this; return new Bucket<E>(buildTick, filterChanged, container); } } }