[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 &quot;JAR:&quot;+ has11TLD + jar path 
 		 *                     		 URLRecords as &quot;URL:&quot;+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

+	}

+}