| /******************************************************************************* |
| * Copyright (c) 2005, 2017 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.core; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.dltk.annotations.Internal; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceModuleInfoCache; |
| |
| /** |
| * Used to cache some source module information. All information related to |
| * source module are removed, then source module are changed. |
| */ |
| public class SourceModuleInfoCache implements ISourceModuleInfoCache, |
| IResourceChangeListener, IResourceDeltaVisitor { |
| @Internal |
| final int capacity = ModelCache.DEFAULT_ROOT_SIZE * 50; |
| |
| private final ReferenceQueue<ISourceModuleInfo> queue = new ReferenceQueue<>(); |
| |
| @SuppressWarnings("serial") |
| private final Map<ISourceModule, CacheReference> map = new LinkedHashMap<ISourceModule, CacheReference>( |
| 16, 0.9f, true) { |
| @Override |
| protected boolean removeEldestEntry( |
| Map.Entry<ISourceModule, CacheReference> eldest) { |
| return size() > capacity; |
| } |
| }; |
| |
| private static class CacheReference |
| extends SoftReference<ISourceModuleInfo> { |
| final long modificationStamp; |
| final ISourceModule module; |
| |
| public CacheReference(ISourceModule module, ISourceModuleInfo referent, |
| ReferenceQueue<? super ISourceModuleInfo> q) { |
| super(referent, q); |
| this.module = module; |
| this.modificationStamp = getModificationStamp(module); |
| } |
| |
| private static long getModificationStamp(ISourceModule module) { |
| final IResource resource = module.getResource(); |
| return resource != null ? resource.getModificationStamp() |
| : IResource.NULL_STAMP; |
| } |
| |
| public boolean isValid(ISourceModule module) { |
| final IResource resource = module.getResource(); |
| return resource == null |
| || resource.getModificationStamp() == modificationStamp; |
| } |
| |
| } |
| |
| public void start() { |
| DLTKCore.addPreProcessingResourceChangedListener(this, |
| IResourceChangeEvent.POST_CHANGE); |
| } |
| |
| public void stop() { |
| DLTKCore.removePreProcessingResourceChangedListener(this); |
| } |
| |
| private void expungeStaleEntries() { |
| for (CacheReference r; (r = (CacheReference) queue.poll()) != null;) { |
| if (DEBUG) { |
| System.out.println( |
| "[Cache] expunge " + r.module.getElementName()); |
| } |
| map.remove(r.module); |
| } |
| } |
| |
| @Override |
| public synchronized ISourceModuleInfo get(ISourceModule module) { |
| expungeStaleEntries(); |
| final CacheReference ref = map.get(module); |
| if (ref != null) { |
| final ISourceModuleInfo info = ref.get(); |
| if (info != null && ref.isValid(module)) { |
| return info; |
| } |
| } |
| final ISourceModuleInfo info = new SourceModuleInfo(); |
| map.put(module, new CacheReference(module, info, queue)); |
| return info; |
| } |
| |
| @Override |
| public synchronized void resourceChanged(IResourceChangeEvent event) { |
| expungeStaleEntries(); |
| final IResourceDelta delta = event.getDelta(); |
| try { |
| delta.accept(this); |
| } catch (CoreException e) { |
| DLTKCore.error(e); |
| } |
| } |
| |
| @Override |
| public boolean visit(IResourceDelta delta) throws CoreException { |
| final int kind = delta.getKind(); |
| if (kind == IResourceDelta.ADDED) { |
| return false; |
| } |
| final IResource resource = delta.getResource(); |
| switch (kind) { |
| case IResourceDelta.CHANGED: |
| switch (resource.getType()) { |
| case IResource.PROJECT: |
| if ((delta.getFlags() & IResourceDelta.OPEN) != 0) { |
| final IProject project = (IProject) resource; |
| if (!project.isOpen()) { |
| removeByProject(project); |
| return false; |
| } |
| } |
| return true; |
| case IResource.FOLDER: |
| return true; |
| case IResource.FILE: |
| if ((delta.getFlags() & IResourceDelta.CONTENT) != 0) { |
| remove((IFile) resource); |
| } |
| break; |
| } |
| break; |
| case IResourceDelta.REMOVED: |
| switch (resource.getType()) { |
| case IResource.PROJECT: |
| removeByProject((IProject) resource); |
| return false; |
| case IResource.FOLDER: |
| return true; |
| case IResource.FILE: |
| remove((IFile) resource); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Internal |
| static class SourceModuleInfo implements ISourceModuleInfo { |
| private Map<Object, Object> map; |
| |
| @Override |
| public synchronized Object get(String key) { |
| if (map == null) { |
| return null; |
| } |
| return map.get(key); |
| } |
| |
| @Override |
| public synchronized void put(String key, Object value) { |
| if (map == null) { |
| map = new HashMap<>(); |
| } |
| map.put(key, value); |
| } |
| |
| @Override |
| public synchronized void remove(String key) { |
| if (map != null) { |
| map.remove(key); |
| } |
| } |
| |
| @Override |
| public synchronized boolean isEmpty() { |
| return this.map == null || this.map.isEmpty(); |
| } |
| } |
| |
| /** |
| * Not synchronized here, as it's called only from |
| * {@link #resourceChanged(IResourceChangeEvent)} which is already |
| * synchronized. |
| */ |
| private void removeByProject(IProject project) { |
| for (Iterator<ISourceModule> i = map.keySet().iterator(); i |
| .hasNext();) { |
| final ISourceModule module = i.next(); |
| if (project.equals(module.getScriptProject().getProject())) { |
| i.remove(); |
| } |
| } |
| } |
| |
| public void remove(IFile file) { |
| remove(DLTKCore.createSourceModuleFrom(file)); |
| } |
| |
| @Override |
| public synchronized void remove(ISourceModule module) { |
| if (DEBUG) { |
| System.out.println("[Cache] remove " + module.getElementName()); //$NON-NLS-1$ |
| } |
| map.remove(module); |
| } |
| |
| private static final boolean DEBUG = false; |
| |
| @Override |
| public synchronized void clear() { |
| // clear out reference queue. |
| while (queue.poll() != null) |
| ; |
| map.clear(); |
| } |
| |
| @Override |
| public synchronized int size() { |
| return map.size(); |
| } |
| |
| @Override |
| public int capacity() { |
| return capacity; |
| } |
| |
| } |