[288877] Support for low memory events in TaglibIndex
diff --git a/bundles/org.eclipse.jst.jsp.core/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.jst.jsp.core/.settings/org.eclipse.jdt.core.prefs index 66ddc69..911428b 100644 --- a/bundles/org.eclipse.jst.jsp.core/.settings/org.eclipse.jdt.core.prefs +++ b/bundles/org.eclipse.jst.jsp.core/.settings/org.eclipse.jdt.core.prefs
@@ -56,7 +56,7 @@ org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning org.eclipse.jdt.core.compiler.problem.typeParameterHiding=ignore org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
diff --git a/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF index cfa3695..07badbe 100644 --- a/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jst.jsp.core/META-INF/MANIFEST.MF
@@ -56,6 +56,7 @@ org.eclipse.wst.validation;bundle-version="[1.2.100,1.3.0)", javax.servlet.jsp;bundle-version="[2.0.0,3.0.0)", org.eclipse.wst.common.project.facet.core;bundle-version="[1.4.0,2.0.0)";resolution:=optional, - org.eclipse.wst.common.modulecore;bundle-version="[1.1.300,2.0.0)";resolution:=optional -Bundle-ActivationPolicy: lazy; exclude:="org.eclipse.jst.jsp.core.internal.contenttype" + org.eclipse.wst.common.modulecore;bundle-version="[1.1.300,2.0.0)";resolution:=optional, + org.eclipse.osgi.services;bundle-version="3.2.0" +Bundle-ActivationPolicy: lazy; exclude:="org.eclipse.jst.jsp.core.internal.contenttype,org.eclipse.jst.jsp.css.core.internal.contenttype" Bundle-RequiredExecutionEnvironment: J2SE-1.4
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/ProjectDescription.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/ProjectDescription.java index 0eff764..f5a9c04 100644 --- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/ProjectDescription.java +++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/ProjectDescription.java
@@ -2096,7 +2096,7 @@ /** * <pre> - * 1.0.1 + * 1.1.0 * Save classpath information (| is field delimiter) * Jars are saved as "JAR:"+ has11TLD + jar path * URLRecords as "URL:"+URL
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/TaglibIndex.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/TaglibIndex.java index 0e2122f..8ff9d11 100644 --- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/TaglibIndex.java +++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/taglib/TaglibIndex.java
@@ -15,8 +15,8 @@ import java.lang.ref.Reference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.zip.CRC32; @@ -46,7 +46,9 @@ import org.eclipse.jst.jsp.core.internal.JSPCorePlugin; import org.eclipse.jst.jsp.core.internal.Logger; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager; +import org.eclipse.wst.sse.core.internal.util.AbstractMemoryListener; import org.osgi.framework.Bundle; +import org.osgi.service.event.Event; /** * A non-extendable index manager for taglibs similar to the previous J2EE @@ -116,7 +118,7 @@ // removing the index file ensures that we // don't get stale data if the project is // reopened - removeIndex(proj.getProject()); + removeIndexFile(proj.getProject()); } } } @@ -329,6 +331,79 @@ } } + /** + * An implementation of {@link Map} that has a limit to the number + * of {@link Map.Entry}s it can store. If the limit is reached then the + * oldest {@link Map.Entry}s are automatically removed. + */ + private class LimitedHashMap extends LinkedHashMap { + /** + * Default + */ + private static final long serialVersionUID = 1L; + + /** + * the maximum number of {@link Map.Entry}s this map can store + */ + private int fLimit; + + LimitedHashMap(int limit) { + super(limit); + fLimit = limit; + } + + /** + * If the size of this map has increased passed the limit then return + * <code>true</code>, <code>false</code> otherwise. + * + * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry) + */ + protected boolean removeEldestEntry(Map.Entry eldest) { + boolean willRemove = this.size() > fLimit; + if (willRemove) { + // save its references to disk before it gets bumped + ((ProjectDescription) eldest.getValue()).saveReferences(); + } + return willRemove; + } + } + + /** + * <p>A {@link AbstractMemoryListener} that clears the {@link ProjectDescription} cache + * whenever specific memory events are received.</p> + * + * <p>Events: + * <ul> + * <li>{@link AbstractMemoryListener#SEV_SERIOUS}</li> + * <li>{@link AbstractMemoryListener#SEV_CRITICAL}</li> + * </ul> + * </p> + */ + private class MemoryListener extends AbstractMemoryListener { + /** + * <p>Constructor causes this listener to listen for specific memory events.</p> + * <p>Events: + * <ul> + * <li>{@link AbstractMemoryListener#SEV_SERIOUS}</li> + * <li>{@link AbstractMemoryListener#SEV_CRITICAL}</li> + * </ul> + * </p> + */ + MemoryListener() { + super(new String[] { SEV_SERIOUS, SEV_CRITICAL }); + } + + /** + * On any memory event we handle clear out the project descriptions + * + * @see org.eclipse.jst.jsp.core.internal.util.AbstractMemoryListener#handleMemoryEvent(org.osgi.service.event.Event) + */ + protected void handleMemoryEvent(Event event) { + clearProjectDescriptions(); + } + + } + static final boolean _debugChangeListener = false; static boolean _debugEvents = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/taglib/events")); //$NON-NLS-1$ //$NON-NLS-2$ @@ -347,6 +422,11 @@ private static final String DIRTY = "DIRTY"; static boolean ENABLED = false; + /** + * The minimum limitation on the number of project descriptions to keep cached. + */ + private static final int MINIMUM_LIMIT_FOR_PROJECT_DESCRIPTIONS_CACHE = 3; + static final ILock LOCK = Job.getJobManager().newLock(); /** @@ -537,8 +617,8 @@ public static void shutdown() { try { LOCK.acquire(); - if (_instance.isInitialized()) { - _instance.stop(); + if (getInstance().isInitialized()) { + getInstance().stop(); } } finally { @@ -573,6 +653,11 @@ private ResourceChangeListener fResourceChangeListener; private ITaglibIndexListener[] fTaglibIndexListeners = null; + + /** + * Used to keep the {@link ProjectDescription} cache clean when memory is low + */ + private MemoryListener fMemoryListener; /** symbolic name for OSGI framework */ private final static String OSGI_FRAMEWORK_ID = "org.eclipse.osgi"; //$NON-NLS-1$ @@ -601,12 +686,16 @@ removeIndexes(false); } - fProjectDescriptions = new Hashtable(); + fProjectDescriptions = new LimitedHashMap(calculateCacheLimit()); fResourceChangeListener = new ResourceChangeListener(); fClasspathChangeListener = new ClasspathChangeListener(); + fMemoryListener = new MemoryListener(); + if (ENABLED) { ResourcesPlugin.getWorkspace().addResourceChangeListener(fResourceChangeListener, IResourceChangeEvent.POST_CHANGE); JavaCore.addElementChangedListener(fClasspathChangeListener); + //register the memory listener + fMemoryListener.connect(); } setIntialized(true); } @@ -629,7 +718,7 @@ * Based on org.eclipse.jdt.internal.core.search.indexing.IndexManager * * @param containerPath - * @return + * @return the index file location for the given workspace path */ String computeIndexLocation(IPath containerPath) { String fileName = computeIndexName(containerPath); @@ -649,7 +738,7 @@ /** * @param project - * @return + * @return the ProjectDescription representing the given project */ ProjectDescription createDescription(IProject project) { if (fProjectDescriptions == null) @@ -859,7 +948,7 @@ /** * Removes index file for the given project. */ - private void removeIndex(IProject project) { + void removeIndexFile(IProject project) { File indexFile = new File(computeIndexLocation(project.getFullPath())); if (indexFile.exists()) { indexFile.delete(); @@ -919,6 +1008,8 @@ ResourcesPlugin.getWorkspace().removeResourceChangeListener(fResourceChangeListener); JavaCore.removeElementChangedListener(fClasspathChangeListener); + //unregister the memory listener + fMemoryListener.disconnect(); /* * Clearing the existing saved states helps prune dead data from @@ -926,6 +1017,23 @@ */ removeIndexes(true); + clearProjectDescriptions(); + + setState(CLEAN); + fProjectDescriptions = null; + fResourceChangeListener = null; + fClasspathChangeListener = null; + fMemoryListener = null; + } + } + + /** + * Have all of the ProjectDescriptions write their information to disk and + * then clear our map of them + */ + void clearProjectDescriptions() { + try { + LOCK.acquire(); Iterator i = fProjectDescriptions.values().iterator(); while (i.hasNext()) { ProjectDescription description = (ProjectDescription) i.next(); @@ -933,11 +1041,8 @@ } fProjectDescriptions.clear(); - - setState(CLEAN); - fProjectDescriptions = null; - fResourceChangeListener = null; - fClasspathChangeListener = null; + } finally { + LOCK.release(); } } @@ -948,4 +1053,22 @@ private void setIntialized(boolean intialized) { this.initialized = intialized; } + + /** + * <p>Calculate the maximum number of project descriptions to keep cached.</p> + * <p>Calculated as:<br /> + * <code>MINIMUM_LIMIT_FOR_PROJECT_DESCRIPTIONS_CACHE + log(currentWorkspaceProjectCount)</code></p> + * + * @return the maximum number of project descriptions to keep cached + */ + private int calculateCacheLimit() { + int limit = MINIMUM_LIMIT_FOR_PROJECT_DESCRIPTIONS_CACHE; + + int projectCount = ResourcesPlugin.getWorkspace().getRoot().getProjects().length; + if(projectCount > 0) { + limit += Math.log(projectCount); + } + + return limit; + } }
diff --git a/bundles/org.eclipse.wst.sse.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.wst.sse.core/META-INF/MANIFEST.MF index bd57e6a..2ce0ece 100644 --- a/bundles/org.eclipse.wst.sse.core/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.wst.sse.core/META-INF/MANIFEST.MF
@@ -46,6 +46,7 @@ org.eclipse.wst.common.uriresolver;bundle-version="[1.1.301,1.2.0)", org.eclipse.emf.common;bundle-version="[2.4.0,3.0.0)", org.eclipse.wst.validation;bundle-version="[1.2.100,2.0.0)";resolution:=optional, - org.eclipse.core.expressions;bundle-version="[3.4.100,4.0.0)" + org.eclipse.core.expressions;bundle-version="[3.4.100,4.0.0)", + org.eclipse.osgi.services;bundle-version="3.2.0" Bundle-ActivationPolicy: lazy;exclude:="org.eclipse.wst.sse.core.internal.propertytester" Bundle-RequiredExecutionEnvironment: J2SE-1.4
diff --git a/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java b/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java new file mode 100644 index 0000000..46c60a8 --- /dev/null +++ b/bundles/org.eclipse.wst.sse.core/src/org/eclipse/wst/sse/core/internal/util/AbstractMemoryListener.java
@@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2009 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 + * + *******************************************************************************/ +package org.eclipse.wst.sse.core.internal.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.Platform; +import org.eclipse.wst.sse.core.internal.Logger; +import org.eclipse.wst.sse.core.internal.SSECorePlugin; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; + +/** + * This responds to memory events. + * + * Create an instance of a child of this class with the events you are interested in. + * Then call connect() to start listening. To stop listening call disconnect(); + */ +public abstract class AbstractMemoryListener implements EventHandler { + /** + * The event that indicates that memory is running low at the lowest severity. + * Listeners are requested to release caches that can easily be recomputed. + * The Java VM is not seriously in trouble, but process size is getting higher than + * is deemed acceptable. + */ + public static final String SEV_NORMAL = "org/eclipse/equinox/events/MemoryEvent/NORMAL"; //$NON-NLS-1$ + + /** + * The event that indicates that memory is running low at medium severity. + * Listeners are requested to release intermediate build results, complex models, etc. + * Memory is getting low and may cause operating system level stress, such as swapping. + */ + public static final String SEV_SERIOUS = "org/eclipse/equinox/events/MemoryEvent/SERIOUS"; //$NON-NLS-1$ + + /** + * The event that indicates that memory is running low at highest severity. + * Listeners are requested to do things like close editors and perspectives, close database connections, etc. + * Restoring these resources and caches constitutes lots of work, but memory is so low that + * drastic measures are required. + */ + public static final String SEV_CRITICAL = "org/eclipse/equinox/events/MemoryEvent/CRITICAL"; //$NON-NLS-1$ + + /** + * All of the valid memory severities + */ + public static final String[] SEV_ALL = { SEV_NORMAL, SEV_SERIOUS, SEV_CRITICAL }; + + /** + * Used to register the {@link EventAdmin} listener + */ + private static BundleContext CONTEXT = + (SSECorePlugin.getDefault() != null) ? + SSECorePlugin.getDefault().getBundle().getBundleContext() : null; + + /** + * the severities that will be reacted to + */ + private final List fSeverities; + + /** + * service used to register this listener + */ + private ServiceRegistration fRegisterService; + + /** + * Will listen to all memory events + */ + public AbstractMemoryListener() { + this(AbstractMemoryListener.SEV_ALL); + } + + /** + * Will listen to memory events of the given <code>severity</code> + * + * @param severity listen for memory events of this severity + */ + public AbstractMemoryListener(String severity) { + Assert.isNotNull(severity, "Severity can not be null"); //$NON-NLS-1$ + + List severities = new ArrayList(1); + severities.add(severity); + fSeverities = severities; + } + + /** + * Will listen to memory events of the given <code>severities</code> + * + * @param severities listen for memory events for any of these severities + */ + public AbstractMemoryListener(String[] severities) { + Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$ + Assert.isLegal(severities.length > 0, "Severities must specify at least one severity"); //$NON-NLS-1$ + + fSeverities = Arrays.asList(severities); + } + + /** + * Will listen to memory events of the given <code>severities</code> + * + * @param severities listen for memory events for any of these severities + */ + public AbstractMemoryListener(List severities) { + Assert.isNotNull(severities, "Severities can not be null"); //$NON-NLS-1$ + Assert.isLegal(!severities.isEmpty(), "Severities must specify at least one severity"); //$NON-NLS-1$ + fSeverities = severities; + } + + /** + * Connect this listener to the {@link EventAdmin} + */ + public final void connect() { + if (CONTEXT != null) { + // NOTE: This is TEMPORARY CODE needed to load the plugin + // until its done automatically by the product + // TODO: Remove me + Bundle b = Platform.getBundle("org.eclipse.equinox.event"); //$NON-NLS-1$ + if (b != null && b.getState() == Bundle.RESOLVED) { + try { + b.start(Bundle.START_TRANSIENT); + } + catch (BundleException e) { + e.printStackTrace(); + } + } + // end remove me + + //register this handler + String[] severities = (String[])fSeverities.toArray(new String[fSeverities.size()]); + Hashtable prop = new Hashtable(1); + prop.put(EventConstants.EVENT_TOPIC, severities); + fRegisterService = CONTEXT.registerService(EventHandler.class.getName(), this, prop); + + //call any implementer specific connect code + doConnect(); + } else { + Logger.log(Logger.WARNING, "Error accessing bundle context. Is Platform running? Not tracking memory events. "); //$NON-NLS-1$ + } + } + + /** + * Disconnect this listener to the {@link EventAdmin} + */ + public final void disconnect() { + if (fRegisterService != null) { + fRegisterService.unregister(); + fRegisterService = null; + } + + //call any implementer specific disconnect code + doDisconnect(); + } + + /** + * <p>Filter out any events that are not of the type that this listener handles</p> + * + * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event) + */ + public final void handleEvent(Event event) { + if (fSeverities.contains(event.getTopic())) { + handleMemoryEvent(event); + } + } + + /** + * Implementing child classes may assume that only {@link Event}s of the types + * given to the constructor will be given to this method. + * + * @param event the {@link Event} with a topic equal to one of the memory + * severities that this listener is listening for + */ + protected abstract void handleMemoryEvent(Event event); + + /** + * Implementers may overrun this method to do setup after connection of this listener + */ + protected void doConnect() { + //do nothing by default + } + + /** + * Implementers may overrun this method to do tear down after disconnection of this listener + */ + protected void doDisconnect() { + //do nothing by default + } +}