/******************************************************************************* * Copyright (c) 2009, 2016 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 * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.corext.util; import java.util.*; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ITypeHierarchy; import org.eclipse.dltk.core.ITypeHierarchyChangedListener; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.internal.core.hierarchy.TypeHierarchy; import org.eclipse.dltk.internal.core.util.MethodOverrideTester; import org.eclipse.php.core.PHPToolkitUtil; /** * TODO : should be in the DLTK ui * * @author roy * */ public class SuperTypeHierarchyCache { private static class FakeTypeHierarchy extends TypeHierarchy { public FakeTypeHierarchy() { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=494388 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=498339 // We must initialize the internal properties to avoid NPEs // when using this class methods. initialize(1); } } private static class HierarchyCacheEntry implements ITypeHierarchyChangedListener { private ITypeHierarchy fTypeHierarchy; private long fLastAccess; public HierarchyCacheEntry(ITypeHierarchy hierarchy) { fTypeHierarchy = hierarchy; fTypeHierarchy.addTypeHierarchyChangedListener(this); markAsAccessed(); } @Override public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) { removeHierarchyEntryFromCache(this); } public ITypeHierarchy getTypeHierarchy() { return fTypeHierarchy; } public void markAsAccessed() { fLastAccess = System.currentTimeMillis(); } public long getLastAccess() { return fLastAccess; } public void dispose() { fTypeHierarchy.removeTypeHierarchyChangedListener(this); fTypeHierarchy = null; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "Super hierarchy of: " + fTypeHierarchy.getType().getElementName(); //$NON-NLS-1$ } } private static final int CACHE_SIZE = 8; private static List<HierarchyCacheEntry> fgHierarchyCache = new ArrayList<>(CACHE_SIZE); private static Map<IType, MethodOverrideTester> fgMethodOverrideTesterCache = new LRUMap(CACHE_SIZE); private static int fgCacheHits = 0; private static int fgCacheMisses = 0; /** * Get a hierarchy for the given type */ public static ITypeHierarchy getTypeHierarchy(IType type) throws ModelException { return getTypeHierarchy(type, null); } public static MethodOverrideTester getMethodOverrideTester(IType type) throws ModelException { MethodOverrideTester test = null; synchronized (fgMethodOverrideTesterCache) { test = fgMethodOverrideTesterCache.get(type); } if (test == null) { ITypeHierarchy hierarchy = getTypeHierarchy(type); // don't nest the // locks synchronized (fgMethodOverrideTesterCache) { test = fgMethodOverrideTesterCache.get(type); // test // again // after // waiting // a // long // time // for // 'getTypeHierarchy' if (test == null) { test = new MethodOverrideTester(type, hierarchy); fgMethodOverrideTesterCache.put(type, test); } } } return test; } private static void removeMethodOverrideTester(ITypeHierarchy hierarchy) { synchronized (fgMethodOverrideTesterCache) { for (Iterator<MethodOverrideTester> iter = fgMethodOverrideTesterCache.values().iterator(); iter .hasNext();) { MethodOverrideTester curr = iter.next(); if (curr != null && curr.getTypeHierarchy() != null && curr.getTypeHierarchy().equals(hierarchy)) { iter.remove(); } } } } /** * Get a hierarchy for the given type * * @throws ModelException */ public static ITypeHierarchy getTypeHierarchy(IType type, IProgressMonitor progressMonitor) throws ModelException { if (type == null || !type.exists() || !PHPToolkitUtil.isFromPHPProject(type)) { return new FakeTypeHierarchy(); } ITypeHierarchy hierarchy = findTypeHierarchyInCache(type); if (hierarchy == null) { fgCacheMisses++; hierarchy = type.newSupertypeHierarchy(progressMonitor); addTypeHierarchyToCache(hierarchy); } else { fgCacheHits++; } return hierarchy; } private static void addTypeHierarchyToCache(ITypeHierarchy hierarchy) { synchronized (fgHierarchyCache) { int nEntries = fgHierarchyCache.size(); if (nEntries >= CACHE_SIZE) { // find obsolete entries or remove entry that was least recently // accessed HierarchyCacheEntry oldest = null; List<HierarchyCacheEntry> obsoleteHierarchies = new ArrayList<>(CACHE_SIZE); for (int i = 0; i < nEntries; i++) { HierarchyCacheEntry entry = fgHierarchyCache.get(i); ITypeHierarchy curr = entry.getTypeHierarchy(); if (!curr.exists() || hierarchy.contains(curr.getType())) { obsoleteHierarchies.add(entry); } else { if (oldest == null || entry.getLastAccess() < oldest.getLastAccess()) { oldest = entry; } } } if (!obsoleteHierarchies.isEmpty()) { for (int i = 0; i < obsoleteHierarchies.size(); i++) { removeHierarchyEntryFromCache(obsoleteHierarchies.get(i)); } } else if (oldest != null) { removeHierarchyEntryFromCache(oldest); } } HierarchyCacheEntry newEntry = new HierarchyCacheEntry(hierarchy); fgHierarchyCache.add(newEntry); } } /** * Check if the given type is in the hierarchy * * @param type * @return Return <code>true</code> if a hierarchy for the given type is * cached. */ public static boolean hasInCache(IType type) { return findTypeHierarchyInCache(type) != null; } private static ITypeHierarchy findTypeHierarchyInCache(IType type) { if (type != null) { synchronized (fgHierarchyCache) { for (int i = fgHierarchyCache.size() - 1; i >= 0; i--) { HierarchyCacheEntry curr = fgHierarchyCache.get(i); ITypeHierarchy hierarchy = curr.getTypeHierarchy(); if (!hierarchy.exists()) { removeHierarchyEntryFromCache(curr); } else { if (hierarchy.contains(type)) { curr.markAsAccessed(); return hierarchy; } } } } } return null; } private static void removeHierarchyEntryFromCache(HierarchyCacheEntry entry) { synchronized (fgHierarchyCache) { removeMethodOverrideTester(entry.getTypeHierarchy()); entry.dispose(); fgHierarchyCache.remove(entry); } } /** * Gets the number of times the hierarchy could be taken from the hierarchy. * * @return Returns a int */ public static int getCacheHits() { return fgCacheHits; } /** * Gets the number of times the hierarchy was build. Used for testing. * * @return Returns a int */ public static int getCacheMisses() { return fgCacheMisses; } /** * */ public static class LRUMap extends LinkedHashMap<IType, MethodOverrideTester> { private static final long serialVersionUID = 1L; private final int fMaxSize; public LRUMap(int maxSize) { super(maxSize, 0.75f, true); fMaxSize = maxSize; } @Override protected boolean removeEldestEntry(java.util.Map.Entry<IType, MethodOverrideTester> eldest) { return size() > fMaxSize; } } }