| /******************************************************************************* |
| * 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.core.mixin; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| 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.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.dltk.compiler.CharOperation; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.ElementChangedEvent; |
| import org.eclipse.dltk.core.IDLTKLanguageToolkit; |
| import org.eclipse.dltk.core.IElementChangedListener; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementDelta; |
| import org.eclipse.dltk.core.IScriptFolder; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.RuntimePerformanceMonitor; |
| import org.eclipse.dltk.core.RuntimePerformanceMonitor.PerformanceNode; |
| import org.eclipse.dltk.core.mixin.IMixinRequestor.ElementInfo; |
| import org.eclipse.dltk.core.search.IDLTKSearchScope; |
| import org.eclipse.dltk.core.search.SearchEngine; |
| import org.eclipse.dltk.core.search.indexing.IIndexConstants; |
| import org.eclipse.dltk.internal.core.OverflowingLRUCache; |
| import org.eclipse.dltk.internal.core.mixin.IInternalMixinElement; |
| import org.eclipse.dltk.internal.core.mixin.MixinCache; |
| import org.eclipse.dltk.internal.core.mixin.MixinManager; |
| import org.eclipse.dltk.internal.core.util.LRUCache; |
| |
| public class MixinModel { |
| private static final long REQUEST_CACHE_EXPIRE_TIME = 2000; |
| private static final boolean DEBUG = false; |
| private static final boolean TRACE = false; |
| |
| public static final String SEPARATOR = String |
| .valueOf(IIndexConstants.SEPARATOR); |
| |
| private static final int CACHE_LIMIT = 250000; |
| private static final int KEYS_CACHE_LIMIT = 500000; |
| private static final int REQUEST_CACHE_LIMIT = 500; |
| |
| private final MixinCache cache; |
| |
| /** |
| * Contains map of source modules to mixin elements. |
| */ |
| private Map<ISourceModule, List<MixinElement>> elementToMixinCache = new HashMap<>(); |
| |
| private final RequestCache requestCache = new RequestCache( |
| REQUEST_CACHE_LIMIT); |
| |
| // true if exists, false if doesn't |
| private Map<String, Boolean> knownKeysCache = new HashMap<>(); |
| // boolean, for the atomicity of it |
| public boolean removes = true; |
| |
| private final IDLTKLanguageToolkit toolkit; |
| |
| private final IScriptProject project; |
| |
| private MixinRequestor mixinRequestor = new MixinRequestor(); |
| |
| private ISourceModule currentModule; |
| |
| /** |
| * modules required to be reparsed |
| */ |
| private Set<ISourceModule> modulesToReparse = new HashSet<>(); |
| |
| /** |
| * Creates workspace instance |
| * |
| * @param toolkit |
| */ |
| public MixinModel(IDLTKLanguageToolkit toolkit) { |
| this(toolkit, null); |
| } |
| |
| /** |
| * Creates project instance |
| * |
| * @param toolkit |
| * @param project |
| */ |
| public MixinModel(IDLTKLanguageToolkit toolkit, IScriptProject project) { |
| this.toolkit = toolkit; |
| this.project = project; |
| |
| // long maxMemory = Runtime.getRuntime().freeMemory(); |
| |
| this.cache = new MixinCache(CACHE_LIMIT); |
| DLTKCore.addElementChangedListener(changedListener, |
| ElementChangedEvent.POST_CHANGE); |
| ResourcesPlugin.getWorkspace() |
| .addResourceChangeListener(changedListener); |
| MixinModelRegistry.register(this); |
| } |
| |
| public void stop() { |
| DLTKCore.removeElementChangedListener(changedListener); |
| ResourcesPlugin.getWorkspace() |
| .removeResourceChangeListener(changedListener); |
| MixinModelRegistry.unregister(this); |
| } |
| |
| // long-running operation |
| public IMixinElement get(String key) { |
| if (DLTKCore.VERBOSE) { |
| System.out.println("MixinModel.get(" + key + ')'); //$NON-NLS-1$ |
| } |
| MixinElement element = null; |
| synchronized (this) { |
| if (knownKeysCache.get(key) == Boolean.FALSE) { |
| return null; |
| } |
| element = (MixinElement) cache.get(key); |
| if (element == null) { |
| if (!removes) { |
| return null; |
| } else { |
| element = new MixinElement(key, currentModule); |
| cache.put(key, element); |
| cache.ensureSpaceLimit(1, element); |
| } |
| } |
| if (DLTKCore.VERBOSE) { |
| System.out |
| .println("Filling ratio:" + this.cache.fillingRatio()); //$NON-NLS-1$ |
| this.cache.printStats(); |
| } |
| } |
| buildElementTree(element); |
| synchronized (this) { |
| if (element.isFinal() && element.sourceModules.size() > 0) { |
| knownKeysCache.put(key, Boolean.TRUE); |
| return element; |
| } |
| knownKeysCache.put(key, Boolean.FALSE); |
| cache.remove(element.key); |
| cache.resetSpaceLimit(CACHE_LIMIT, element); |
| } |
| return null; |
| } |
| |
| private IDLTKSearchScope createSearchScope() { |
| if (project != null) { |
| return SearchEngine.createSearchScope(project); |
| } else { |
| return SearchEngine.createWorkspaceScope(toolkit); |
| } |
| } |
| |
| private static class RequestCacheEntry { |
| long expireTime; |
| String prefix = null; |
| Set<ISourceModule> modules = null; |
| Set<String> keys = null; |
| } |
| |
| private static class RequestCache extends OverflowingLRUCache { |
| |
| public RequestCache(int size) { |
| super(size); |
| } |
| |
| public RequestCache(int size, int overflow) { |
| super(size, overflow); |
| } |
| |
| @Override |
| protected boolean close(LRUCacheEntry entry) { |
| return true; |
| } |
| |
| @Override |
| protected LRUCache newInstance(int size, int overflow) { |
| return new RequestCache(size, overflow); |
| } |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| public IMixinElement[] find(String pattern, long delta) { |
| return find(pattern, new NullProgressMonitor()); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public IMixinElement[] find(String pattern, IProgressMonitor monitor) { |
| // long-running operation |
| long start = TRACE ? System.currentTimeMillis() : 0; |
| |
| RequestCacheEntry entry = findFromMixin(pattern, monitor); |
| |
| if (entry.modules == null || entry.modules.size() == 0) { |
| return new IMixinElement[0]; |
| } |
| |
| long parses = TRACE ? System.currentTimeMillis() : 0; |
| for (ISourceModule module : entry.modules) { |
| reportModule(module); |
| } |
| long parsee = TRACE ? System.currentTimeMillis() : 0; |
| |
| Set<MixinElement> result = new HashSet<>(); |
| |
| synchronized (this) { |
| for (String key : entry.keys) { |
| MixinElement element = getCreateEmpty(key); |
| if (!monitor.isCanceled()) { |
| markElementAsFinal(element); |
| } |
| addKeyToSet(result, element, pattern); |
| } |
| } |
| if (TRACE) { |
| long end = System.currentTimeMillis(); |
| System.out.println("MixinModel::find.time:" //$NON-NLS-1$ |
| + String.valueOf(end - start)); |
| System.out.println("MixinModel::find.parsetime:" //$NON-NLS-1$ |
| + String.valueOf(parsee - parses)); |
| } |
| |
| return result.toArray(new IMixinElement[result.size()]); |
| } |
| |
| // called with lock being held |
| private void addKeyToSet(Set<MixinElement> result, MixinElement element, |
| String pattern) { |
| // Skip all not matched keys |
| if (!CharOperation.match(pattern.toCharArray(), |
| element.key.toCharArray(), true)) { |
| return; |
| } |
| result.add(element); |
| knownKeysCache.put(element.key, Boolean.TRUE); |
| for (MixinElement child : element.children) |
| addKeyToSet(result, child, pattern); |
| } |
| |
| // long-running operation |
| private RequestCacheEntry findFromMixin(String pattern, |
| IProgressMonitor monitor) { |
| PerformanceNode p = RuntimePerformanceMonitor.begin(); |
| RequestCacheEntry entry; |
| synchronized (this) { |
| entry = (RequestCacheEntry) requestCache.get(pattern); |
| if (entry != null && entry.expireTime >= System.currentTimeMillis()) |
| return entry; |
| entry = new RequestCacheEntry(); |
| // TODO searches with clashing keys |
| // requestCache.put(pattern, entry); |
| } |
| Map<ISourceModule, Set<String>> keys = new HashMap<>(); |
| ISourceModule[] containedModules = null; |
| try { |
| containedModules = SearchEngine.searchMixinSources( |
| createSearchScope(), pattern, toolkit, keys, monitor); |
| } catch (OperationCanceledException e) { |
| return entry; |
| } |
| entry.expireTime = System.currentTimeMillis() |
| + REQUEST_CACHE_EXPIRE_TIME; |
| entry.modules = new HashSet<>( |
| Arrays.asList(containedModules)); |
| entry.prefix = pattern; |
| entry.keys = new HashSet<>(); |
| for (Set<String> strs : keys.values()) { |
| entry.keys.addAll(strs); |
| } |
| if (!monitor.isCanceled()) { |
| synchronized (this) { |
| requestCache.put(pattern, entry); |
| } |
| } |
| p.done(getNature(), "Mixin model search items", 0); |
| return entry; |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| public IMixinElement[] find(String pattern) { |
| return find(pattern, new NullProgressMonitor()); |
| } |
| |
| /** |
| * @deprecated |
| */ |
| @Deprecated |
| public String[] findKeys(String pattern) { |
| return findKeys(pattern, new NullProgressMonitor()); |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| public String[] findKeys(String pattern, IProgressMonitor monitor) { |
| RequestCacheEntry entry = findFromMixin(pattern, monitor); |
| return entry.keys.toArray(new String[entry.keys.size()]); |
| } |
| |
| // long-running operation |
| public boolean keyExists(String key) { |
| synchronized (this) { |
| // TODO: For this version we cache all information, so should be |
| // false. |
| if (!removes) { |
| return cache.get(key) != null; |
| } |
| MixinElement e = (MixinElement) this.cache.get(key); |
| if (e != null && e.sourceModules.size() > 0) { |
| return true; |
| } |
| Boolean cached = knownKeysCache.get(key); |
| if (cached != null) |
| return cached; |
| } |
| boolean exists = get(key) != null; |
| synchronized (this) { |
| if (knownKeysCache.size() > KEYS_CACHE_LIMIT) { |
| knownKeysCache.clear(); |
| } |
| knownKeysCache.put(key, exists); |
| } |
| return exists; |
| } |
| |
| // long-running operation |
| private void buildElementTree(MixinElement element) { |
| // TODO: This is consistent cache stage |
| if (element.isFinal()) { |
| return; |
| } |
| ISourceModule[] containedModules = findModules(element.getKey()); |
| if (containedModules.length == 0) { |
| synchronized (cache) { |
| cache.remove(element.key); |
| cache.resetSpaceLimit(CACHE_LIMIT, element); |
| } |
| return; |
| } |
| for (ISourceModule module : containedModules) { |
| reportModule(module); |
| } |
| // mark selected element and all subelements as finished. |
| synchronized (this) { |
| markElementAsFinal(element); |
| } |
| } |
| |
| // called with lock being held |
| private void markElementAsFinal(MixinElement element) { |
| element.bFinal = true; |
| for (MixinElement child : element.children) { |
| markElementAsFinal(child); |
| } |
| } |
| |
| // TODO long-running operation. shouldn't be synchronized |
| public synchronized void reportModule(ISourceModule sourceModule) { |
| if (!elementToMixinCache.containsKey(sourceModule)) { |
| elementToMixinCache.put(sourceModule, |
| new ArrayList<MixinElement>()); |
| } else { |
| // Module already in model. So we do not to rebuild it. |
| if (!modulesToReparse.remove(sourceModule)) { |
| return; |
| } |
| // We need to reparse module if some elements are moved from it. |
| } |
| try { |
| IMixinParser mixinParser = MixinManager |
| .getMixinParser(sourceModule); |
| if (mixinParser != null) { |
| this.currentModule = sourceModule; |
| mixinParser.setRequirestor(mixinRequestor); |
| mixinParser.parserSourceModule(true, sourceModule); |
| this.currentModule = null; |
| } |
| } catch (CoreException e) { |
| DLTKCore.error("Error in reportModule", e); //$NON-NLS-1$ |
| return; |
| } |
| } |
| |
| /** |
| * Should find all elements source modules to be sure we build complete |
| * child tree. |
| * |
| * @param element |
| * @return |
| * @since 2.0 |
| */ |
| public ISourceModule[] findModules(String key, IProgressMonitor monitor) { |
| RequestCacheEntry entry = findFromMixin(key, monitor); |
| return entry.modules.toArray(new ISourceModule[entry.modules.size()]); |
| } |
| |
| /** |
| * @Deprecated |
| */ |
| public ISourceModule[] findModules(String key) { |
| return findModules(key, new NullProgressMonitor()); |
| } |
| |
| /** |
| * Returns a mixin element from this.cache by it's key, or creates new one |
| * if cache doesn't contain required element |
| * |
| * @param key |
| * @return |
| */ |
| private MixinElement getCreateEmpty(String key) { |
| // called with lock being held |
| MixinElement element = (MixinElement) cache.get(key); |
| if (element == null) { |
| element = new MixinElement(key, currentModule); |
| this.cache.put(key, element); |
| this.cache.ensureSpaceLimit(1, element); |
| } |
| return element; |
| } |
| |
| private interface IMixinChangedListener |
| extends IElementChangedListener, IResourceChangeListener { |
| } |
| |
| private IMixinChangedListener changedListener = new IMixinChangedListener() { |
| @Override |
| public void elementChanged(ElementChangedEvent event) { |
| IModelElementDelta delta = event.getDelta(); |
| synchronized (MixinModel.this) { |
| processDelta(delta); |
| } |
| } |
| |
| // called with lock being held |
| private void processDelta(IModelElementDelta delta) { |
| IModelElement element = delta.getElement(); |
| if (delta.getKind() == IModelElementDelta.REMOVED |
| || delta.getKind() == IModelElementDelta.CHANGED |
| || (delta.getFlags() |
| & IModelElementDelta.F_REMOVED_FROM_BUILDPATH) != 0 |
| || (delta.getFlags() & IModelElementDelta.CHANGED) != 0) { |
| if (element.getElementType() != IModelElement.SOURCE_MODULE |
| && element |
| .getElementType() != IModelElement.PROJECT_FRAGMENT |
| && element |
| .getElementType() != IModelElement.SCRIPT_FOLDER |
| && element |
| .getElementType() != IModelElement.SCRIPT_MODEL |
| && element |
| .getElementType() != IModelElement.SCRIPT_PROJECT) { |
| ISourceModule module = (ISourceModule) element |
| .getAncestor(IModelElement.SOURCE_MODULE); |
| MixinModel.this.remove(module); |
| } |
| if (element.getElementType() == IModelElement.SOURCE_MODULE) { |
| MixinModel.this.remove((ISourceModule) element); |
| } |
| } |
| |
| if (element.getElementType() == IModelElement.SCRIPT_PROJECT |
| && delta.getKind() == IModelElementDelta.CHANGED |
| && (delta.getFlags() |
| & IModelElementDelta.F_BUILDPATH_CHANGED) != 0) { |
| clear(); |
| return; |
| } else if ((delta.getKind() == IModelElementDelta.REMOVED |
| || delta.getKind() == IModelElementDelta.CHANGED) |
| && (element.getElementType() == IModelElement.SCRIPT_FOLDER |
| || element |
| .getElementType() == IModelElement.PROJECT_FRAGMENT)) { |
| if (delta.getAffectedChildren().length == 0) { |
| try { |
| element.accept(element1 -> { |
| if (element1 |
| .getElementType() == ISourceModule.SOURCE_MODULE) { |
| remove((ISourceModule) element1); |
| return false; |
| } |
| return true; |
| }); |
| } catch (ModelException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| if (delta.getKind() == IModelElementDelta.ADDED) { |
| if (element.getElementType() == IModelElement.SOURCE_MODULE) { |
| if (modulesToReparse.add((ISourceModule) element)) { |
| reportModule((ISourceModule) element); |
| } |
| } |
| knownKeysCache.clear(); |
| requestCache.flush(); |
| } |
| |
| if ((delta.getFlags() & IModelElementDelta.F_CHILDREN) != 0) { |
| for (IModelElementDelta child : delta.getAffectedChildren()) { |
| processDelta(child); |
| } |
| } else if (delta.getKind() == IModelElementDelta.REMOVED && element |
| .getElementType() == IModelElement.SCRIPT_FOLDER) { |
| /* folder delete delta has no children */ |
| MixinModel.this.removeFolder((IScriptFolder) element); |
| } |
| } |
| |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| int eventType = event.getType(); |
| IResource resource = event.getResource(); |
| // IResourceDelta delta = event.getDelta(); |
| |
| switch (eventType) { |
| case IResourceChangeEvent.PRE_CLOSE: |
| if (resource.getType() == IResource.PROJECT |
| && DLTKLanguageManager |
| .hasScriptNature((IProject) resource)) { |
| if (project != null |
| && resource.equals(project.getProject())) { |
| clear(); |
| // TODO destroy this model |
| return; |
| } |
| } |
| break; |
| case IResourceChangeEvent.PRE_DELETE: |
| if (resource.getType() == IResource.PROJECT |
| && DLTKLanguageManager |
| .hasScriptNature((IProject) resource)) { |
| if (project != null |
| && resource.equals(project.getProject())) { |
| clear(); |
| // TODO destroy this model |
| return; |
| } |
| // remove all resources with given project from model. |
| List<ISourceModule> toRemove = new ArrayList<>(); |
| synchronized (this) { |
| IProject project = (IProject) resource; |
| for (ISourceModule module : elementToMixinCache |
| .keySet()) { |
| IScriptProject scriptProject = module |
| .getScriptProject(); |
| if (scriptProject != null) { |
| IProject prj = scriptProject.getProject(); |
| if ((prj != null && prj.equals(project)) |
| || prj == null) { |
| toRemove.add(module); |
| } |
| } else { |
| toRemove.add(module); |
| } |
| } |
| for (ISourceModule module : toRemove) { |
| remove(module); |
| } |
| } |
| } |
| return; |
| } |
| } |
| }; |
| |
| // private synchronized void clearAllElementsState() { |
| // Enumeration elements = cache.elements(); |
| // while( elements.hasMoreElements() ) { |
| // MixinElement o = (MixinElement)elements.nextElement(); |
| // o.bFinal = false; |
| // } |
| // } |
| |
| private final String getLogContext() { |
| if (project == null) { |
| return "[MixinModel|$" + toolkit.getLanguageName() + "$]"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| return "[MixinModel|" + project.getElementName() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private final void log(String message) { |
| System.out.println(getLogContext() + " " + message); //$NON-NLS-1$ |
| } |
| |
| public synchronized void remove(ISourceModule element) { |
| if (DEBUG) { |
| log("remove " + element.getElementName()); //$NON-NLS-1$ |
| } |
| List<MixinElement> mixinCache = elementToMixinCache.get(element); |
| if (mixinCache != null) { |
| removeFromRequestCache(element); |
| |
| for (MixinElement mixin : mixinCache) { |
| removes = true; |
| knownKeysCache.remove(mixin.key); |
| mixin.bFinal = false; |
| mixin.sourceModules.remove(element); |
| mixin.sourceModuleToObject.remove(element); |
| if (mixin.sourceModules.size() == 0) { |
| // Remove frob parent. |
| String parentKey = mixin.getParentKey(); |
| if (parentKey != null) { |
| MixinElement parent = (MixinElement) this.cache |
| .get(parentKey); |
| if (parent != null) { |
| parent.children.remove(mixin); |
| parent.bFinal = false; |
| } |
| } |
| // Remove from cache |
| cache.remove(mixin.key); |
| cache.resetSpaceLimit(CACHE_LIMIT, mixin); |
| } |
| } |
| this.elementToMixinCache.remove(element); |
| } |
| } |
| |
| /** |
| * @param folder |
| */ |
| protected synchronized void removeFolder(IScriptFolder folder) { |
| final IPath folderPath = folder.getPath(); |
| final List<ISourceModule> modulesToRemove = new ArrayList<>(); |
| for (final ISourceModule module : elementToMixinCache.keySet()) { |
| final IPath path = module.getPath(); |
| if (folderPath.isPrefixOf(path)) { |
| modulesToRemove.add(module); |
| } |
| } |
| for (ISourceModule module : modulesToRemove) { |
| remove(module); |
| } |
| } |
| |
| // called with lock being held |
| private void removeFromRequestCache(ISourceModule element) { |
| // Clear requests cache. |
| @SuppressWarnings("unchecked") |
| Enumeration<RequestCacheEntry> enumeration = this.requestCache |
| .elements(); |
| while (enumeration.hasMoreElements()) { |
| RequestCacheEntry entry = enumeration.nextElement(); |
| if (entry.modules != null) { |
| if (entry.modules.contains(element)) { |
| // we can do it now |
| this.requestCache.remove(entry.prefix); |
| } |
| } |
| } |
| } |
| |
| /*************************************************************************** |
| * Then getObjects are called, special initialize listener are called. |
| * |
| */ |
| public interface IMixinObjectInitializeListener { |
| void initialize(IMixinElement element, Object object, |
| ISourceModule module); |
| } |
| |
| private final ListenerList mixinObjectInitializeListeners = new ListenerList(); |
| |
| private static final Object[] NO_OBJECTS = new Object[0]; |
| |
| private final class MixinElement |
| implements IMixinElement, IInternalMixinElement { |
| private String key; |
| private boolean bFinal = false; |
| private List<ISourceModule> sourceModules = new ArrayList<>(); |
| private Map<ISourceModule, List<Object>> sourceModuleToObject = new HashMap<>(); |
| |
| private Set<MixinElement> children = new HashSet<>(); |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj instanceof MixinElement) { |
| return this.key.equals(((MixinElement) obj).key); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return this.key.hashCode(); |
| } |
| |
| /* |
| * public MixinElement(String key) { this.key = key; } |
| */ |
| |
| @Override |
| public String toString() { |
| return this.getLastKeySegment() + " final[" + this.bFinal + "]" //$NON-NLS-1$ //$NON-NLS-2$ |
| + this.children + " "; //$NON-NLS-1$ |
| } |
| |
| /* |
| * public MixinElement(ElementInfo info, ISourceModule module) { |
| * this(info.key, currentModule); addInfo(info, module); } |
| */ |
| // called with lock being held |
| void addInfo(ElementInfo info, ISourceModule module) { |
| if (info.object != null) { |
| List<Object> list = this.sourceModuleToObject.get(module); |
| if (list == null) { |
| list = new ArrayList<>(); |
| this.sourceModuleToObject.put(module, list); |
| } |
| list.add(info.object); |
| } |
| } |
| |
| // called with lock being held |
| public MixinElement(String key, ISourceModule currentModule) { |
| this.key = key; |
| addModule(currentModule); |
| } |
| |
| // called with lock being held |
| void addModule(ISourceModule currentModule) { |
| if (currentModule != null) { |
| if (!this.sourceModules.contains(currentModule)) { |
| this.sourceModules.add(currentModule); |
| } |
| } |
| } |
| |
| @Override |
| public IMixinElement[] getChildren() { |
| this.validate(); |
| synchronized (MixinModel.this) { |
| return children.toArray(new IMixinElement[children.size()]); |
| } |
| } |
| |
| @Override |
| public IMixinElement getChildren(String key) { |
| this.validate(); |
| return MixinModel.this |
| .get(this.key + IMixinRequestor.MIXIN_NAME_SEPARATOR + key); |
| } |
| |
| @Override |
| public String getKey() { |
| return this.key; |
| } |
| |
| protected String getParentKey() { |
| int pos = key.lastIndexOf(IMixinRequestor.MIXIN_NAME_SEPARATOR); |
| if (pos == -1) { |
| return null; |
| } |
| return key.substring(0, pos); |
| } |
| |
| @Override |
| public String getLastKeySegment() { |
| int pos = key.lastIndexOf(IMixinRequestor.MIXIN_NAME_SEPARATOR); |
| if (pos == -1) { |
| return key; |
| } |
| return key.substring(pos + 1); |
| } |
| |
| @Override |
| public IMixinElement getParent() { |
| String parentKey = this.getParentKey(); |
| if (parentKey == null) { |
| return null; |
| } |
| return get(parentKey); |
| |
| } |
| |
| @Override |
| public ISourceModule[] getSourceModules() { |
| this.validate(); |
| // TODO understand why we need this |
| if (!isFinal()) { |
| get(key); |
| } |
| synchronized (MixinModel.this) { |
| return this.sourceModules |
| .toArray(new ISourceModule[this.sourceModules.size()]); |
| } |
| } |
| |
| @Override |
| public Object[] getObjects(ISourceModule module) { |
| this.validate(); |
| synchronized (MixinModel.this) { |
| List<Object> list = this.sourceModuleToObject.get(module); |
| if (list == null) |
| return NO_OBJECTS; |
| Object[] objs = list.toArray(); |
| for (Object obj : objs) { |
| notifyInitializeListener(this, module, obj); |
| } |
| return objs; |
| } |
| } |
| |
| @Override |
| public Object[] getAllObjects() { |
| this.validate(); |
| synchronized (MixinModel.this) { |
| Set<Object> objects = new HashSet<>(); |
| for (ISourceModule module : sourceModules) { |
| for (Object obj : this.getObjects(module)) { |
| objects.add(obj); |
| } |
| } |
| return objects.toArray(); |
| } |
| } |
| |
| public boolean isFinal() { |
| return bFinal; |
| } |
| |
| @Override |
| public void close() { |
| synchronized (MixinModel.this) { |
| knownKeysCache.remove(key); |
| removes = true; |
| this.bFinal = false; |
| for (int i = 0; i < sourceModules.size(); i++) { |
| ISourceModule module = sourceModules.get(i); |
| List<MixinElement> list = elementToMixinCache.get(module); |
| if (list != null) { |
| list.remove(this); |
| if (list.size() == 0) { |
| elementToMixinCache.remove(module); |
| } |
| } |
| if (elementToMixinCache.containsKey(module)) { |
| modulesToReparse.add(module); |
| } |
| } |
| this.sourceModules.clear(); |
| this.sourceModuleToObject.clear(); |
| |
| // Lets also clean parent data |
| // Remove frob parent. |
| String parentKey = getParentKey(); |
| MixinElement element = this; |
| while (parentKey != null) { |
| MixinElement parent = (MixinElement) cache.get(parentKey); |
| if (parent != null) { |
| removes = true; |
| knownKeysCache.remove(parent.key); |
| parent.children.remove(element); |
| parent.bFinal = false; |
| element = parent; |
| parentKey = parent.getParentKey(); |
| } else { |
| break; |
| } |
| } |
| } |
| } |
| |
| // potentially long-running operation |
| private void validate() { |
| if (!isFinal()) { |
| buildElementTree(this); |
| } |
| } |
| } |
| |
| private final class MixinRequestor implements IMixinRequestor { |
| @Override |
| public void reportElement(ElementInfo info) { |
| // if( DLTKCore.VERBOSE_MIXIN ) { |
| // System.out.println("Append mixin:" + info.key); |
| // } |
| synchronized (MixinModel.this) { |
| knownKeysCache.put(info.key, Boolean.TRUE); |
| String[] list = info.key.split("\\" //$NON-NLS-1$ |
| + IMixinRequestor.MIXIN_NAME_SEPARATOR); |
| MixinElement element = getCreateEmpty(info.key); |
| addElementToModules(element); |
| element.addModule(currentModule); |
| element.addInfo(info, currentModule); |
| // Append as childs for all other elements. Also append modules |
| // to |
| // all selected elements. |
| if (list.length != 1) { |
| for (int i = 0; i < list.length - 1; ++i) { |
| MixinElement parent = getCreateEmpty( |
| element.getParentKey()); |
| parent.children.add(element); |
| addElementToModules(parent); |
| element = parent; |
| } |
| } |
| } |
| } |
| |
| // called with lock being held |
| private void addElementToModules(MixinElement element) { |
| List<MixinElement> elements = MixinModel.this.elementToMixinCache |
| .get(currentModule); |
| if (elements == null) { |
| elements = new ArrayList<>(); |
| MixinModel.this.elementToMixinCache.put(currentModule, |
| elements); |
| } |
| elements.add(element); |
| } |
| } |
| |
| public synchronized void makeAllModuleElementsFinal(ISourceModule module) { |
| List<MixinElement> elements = elementToMixinCache.get(module); |
| if (elements != null) { |
| for (MixinElement mixin : elements) { |
| removes = true; |
| mixin.bFinal = true; |
| } |
| } |
| } |
| |
| public synchronized void makeAllElementsFinalIfNoCacheRemoves() { |
| if (removes) { |
| return; |
| } |
| Enumeration<?> elements = cache.elements(); |
| while (elements.hasMoreElements()) { |
| MixinElement e = (MixinElement) elements.nextElement(); |
| e.bFinal = true; |
| } |
| |
| } |
| |
| public void setRemovesToZero() { |
| removes = false; |
| } |
| |
| public synchronized void clearKeysCache(String key) { |
| knownKeysCache.remove(key); |
| requestCache.remove(key); |
| // MixinElement e = (MixinElement)this.cache.get(key); |
| } |
| |
| public synchronized void clearKeysCache() { |
| knownKeysCache.clear(); |
| requestCache.flush(); |
| } |
| |
| // // Mixin object initialize listeners code |
| public synchronized void addObjectInitializeListener( |
| IMixinObjectInitializeListener mixinObjectInitializeListener) { |
| this.mixinObjectInitializeListeners.add(mixinObjectInitializeListener); |
| } |
| |
| public synchronized void removeObjectInitializeListener( |
| IMixinObjectInitializeListener mixinObjectInitializeListener) { |
| this.mixinObjectInitializeListeners |
| .remove(mixinObjectInitializeListener); |
| } |
| |
| // called with lock being help |
| private void notifyInitializeListener(IMixinElement element, |
| ISourceModule module, Object o) { |
| Object[] listeners = mixinObjectInitializeListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| ((IMixinObjectInitializeListener) (listeners[i])) |
| .initialize(element, o, module); |
| } |
| } |
| |
| protected synchronized void clear() { |
| cache.flush(); |
| elementToMixinCache.clear(); |
| knownKeysCache.clear(); |
| modulesToReparse.clear(); |
| requestCache.flush(); |
| } |
| |
| public String getNature() { |
| return toolkit.getNatureId(); |
| } |
| } |