[87143] JSP does not respect TEI VariableInfo
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslationAdapter.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslationAdapter.java
index 2a2b502..f0ba288 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslationAdapter.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslationAdapter.java
@@ -37,11 +37,7 @@
 public class JSPTranslationAdapter implements INodeAdapter, IDocumentListener {
 
 	// for debugging
-	private static final boolean DEBUG;
-	static {
-		String value = Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jsptranslation"); //$NON-NLS-1$
-		DEBUG = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$
-	}
+	private static final boolean DEBUG = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jsptranslation")); //$NON-NLS-1$  //$NON-NLS-2$
 
 	private IDocument fJspDocument = null;
 	private IDocument fJavaDocument = null;
@@ -131,7 +127,6 @@
 				translator.translate();
 				StringBuffer javaContents = translator.getTranslation();
 				fJavaDocument = new Document(javaContents.toString());
-
 			}
 			else {
 				// empty document case
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslator.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslator.java
index f3e2708..979333a 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslator.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/JSPTranslator.java
@@ -12,6 +12,7 @@
 
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -21,17 +22,23 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Stack;
-import com.ibm.icu.util.StringTokenizer;
+
+import javax.servlet.jsp.tagext.VariableInfo;
 
 import org.eclipse.core.filebuffers.FileBuffers;
+import org.eclipse.core.filebuffers.ITextFileBuffer;
+import org.eclipse.core.filebuffers.ITextFileBufferManager;
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtension;
 import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.QualifiedName;
@@ -65,6 +72,8 @@
 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
 import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
 
+import com.ibm.icu.util.StringTokenizer;
+
 /**
  * Translates a JSP document into a HttpServlet. Keeps two way mapping from
  * java translation to the original JSP source, which can be obtained through
@@ -87,6 +96,7 @@
 
 	// for debugging
 	private static final boolean DEBUG;
+	private static final boolean DEBUG_SAVE_OUTPUT = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/jsptranslationstodisk")); //$NON-NLS-1$  //$NON-NLS-2$
 
 	private IJSPELTranslator fELTranslator = null;
 
@@ -142,6 +152,12 @@
 	/** user defined imports */
 	private StringBuffer fUserImports = new StringBuffer();
 
+	/**
+	 * A map of tag names to tag library variable information; used to store
+	 * the ones needed for AT_END variable support.
+	 */
+	private HashMap fTagToVariableMap = null;
+
 	private StringBuffer fResult; // the final traslated java document
 	// string buffer
 	private StringBuffer fCursorOwner = null; // the buffer where the cursor
@@ -330,7 +346,8 @@
 					}
 				}
 				catch (CoreException e) {
-					// ISSUE: why do we log this here? Instead of allowing to throwup?
+					// ISSUE: why do we log this here? Instead of allowing to
+					// throwup?
 					Logger.logException(e);
 				}
 
@@ -669,6 +686,29 @@
 			Logger.log(Logger.INFO_DEBUG, debugString.toString());
 		}
 
+		if (DEBUG_SAVE_OUTPUT) {
+			IProject project = getFile().getProject();
+			String shortenedClassname = StringUtils.replace(getFile().getName(), ".", "_");
+			String filename = shortenedClassname + ".java";
+			IPath path = project.getFullPath().append("src/" + filename);
+			try {
+				IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+				if (!file.exists()) {
+					file.create(new ByteArrayInputStream(new byte[0]), true, new NullProgressMonitor());
+				}
+				ITextFileBufferManager textFileBufferManager = FileBuffers.getTextFileBufferManager();
+				textFileBufferManager.connect(path, new NullProgressMonitor());
+				ITextFileBuffer javaOutputBuffer = textFileBufferManager.getTextFileBuffer(path);
+				javaOutputBuffer.getDocument().set(StringUtils.replace(fResult.toString(), getClassname(), shortenedClassname));
+				javaOutputBuffer.commit(new NullProgressMonitor(), true);
+				textFileBufferManager.disconnect(path, new NullProgressMonitor());
+			}
+			catch (Exception e) {
+				// this is just for debugging, ignore
+			}
+			System.out.println("Updated translation: " + path);
+		}
+
 		return fResult;
 	}
 
@@ -690,11 +730,74 @@
 
 		TaglibHelper helper = TaglibHelperManager.getInstance().getTaglibHelper(f);
 		IStructuredDocumentRegion customTag = getCurrentNode();
-		TaglibVariable[] taglibVars = helper.getTaglibVariables(tagToAdd, getStructuredDocument(), customTag);
+		/*
+		 * Variables can declare as available when NESTED, AT_BEGIN, or
+		 * AT_END. For AT_END variables, store the entire list of variables in
+		 * the map field so it can be used on the end tag.
+		 */
 		String decl = ""; //$NON-NLS-1$
-		for (int i = 0; i < taglibVars.length; i++) {
-			decl = taglibVars[i].getDeclarationString();
-			appendToBuffer(decl, fUserCode, false, fCurrentNode);
+		if (customTag.getFirstRegion().getType().equals(DOMRegionContext.XML_TAG_OPEN)) {
+			TaglibVariable[] taglibVars = helper.getTaglibVariables(tagToAdd, getStructuredDocument(), customTag);
+			/*
+			 * These loops are duplicated intentionally to keep the nesting
+			 * scoped variables from interfering with the others
+			 */
+			for (int i = 0; i < taglibVars.length; i++) {
+				if (taglibVars[i].getScope() == VariableInfo.AT_BEGIN) {
+					decl = taglibVars[i].getDeclarationString();
+					appendToBuffer(decl, fUserCode, false, fCurrentNode);
+				}
+				if (taglibVars[i].getScope() == VariableInfo.AT_END) {
+					decl = taglibVars[i].getDeclarationString();
+					fTagToVariableMap.put(tagToAdd, taglibVars);
+				}
+			}
+			for (int i = 0; i < taglibVars.length; i++) {
+				if (taglibVars[i].getScope() == VariableInfo.NESTED) {
+					decl = taglibVars[i].getDeclarationString();
+					appendToBuffer("{", fUserCode, false, fCurrentNode);
+					appendToBuffer(decl, fUserCode, false, fCurrentNode);
+					fTagToVariableMap.put(tagToAdd, taglibVars);
+				}
+			}
+			if (customTag.getLastRegion().getType().equals(DOMRegionContext.XML_EMPTY_TAG_CLOSE)) {
+				/*
+				 * Process NESTED variables backwards so the scopes "unroll"
+				 * correctly.
+				 */
+				for (int i = taglibVars.length; i > 0; i--) {
+					if (taglibVars[i-1].getScope() == VariableInfo.NESTED) {
+						appendToBuffer("}", fUserCode, false, fCurrentNode);
+					}
+				}
+				/* Treat this as the end for empty tags */
+				for (int i = 0; i < taglibVars.length; i++) {
+					if (taglibVars[i].getScope() == VariableInfo.AT_END) {
+						decl = taglibVars[i].getDeclarationString();
+						appendToBuffer(decl, fUserCode, false, fCurrentNode);
+					}
+				}
+			}
+		}
+		/*
+		 * Process NESTED variables for an end tag backwards so the scopes
+		 * "unroll" correctly.
+		 */
+		else if (customTag.getFirstRegion().getType().equals(DOMRegionContext.XML_END_TAG_OPEN)) {
+			TaglibVariable[] taglibVars = (TaglibVariable[]) fTagToVariableMap.remove(tagToAdd);
+			if (taglibVars != null) {
+				for (int i = taglibVars.length; i > 0; i--) {
+					if (taglibVars[i-1].getScope() == VariableInfo.NESTED) {
+						appendToBuffer("}", fUserCode, false, fCurrentNode);
+					}
+				}
+				for (int i = 0; i < taglibVars.length; i++) {
+					if (taglibVars[i].getScope() == VariableInfo.AT_END) {
+						decl = taglibVars[i].getDeclarationString();
+						appendToBuffer(decl, fUserCode, false, fCurrentNode);
+					}
+				}
+			}
 		}
 	}
 
@@ -726,6 +829,10 @@
 	 * structuredDocument nodes
 	 */
 	public void translate() {
+		if (fTagToVariableMap == null) {
+			fTagToVariableMap = new HashMap(2);
+		}
+
 		setCurrentNode(fStructuredDocument.getFirstStructuredDocumentRegion());
 
 		while (getCurrentNode() != null && !isCanceled()) {
@@ -744,6 +851,8 @@
 				advanceNextNode();
 		}
 		buildResult();
+
+		fTagToVariableMap.clear();
 	}
 
 	protected void setDocumentContent(IDocument document, InputStream contentStream, String charset) {
@@ -846,7 +955,7 @@
 				// translateJSPNode(region, regions, type, JSPType);
 				translateJSPNode(containerRegion, regions, type, JSPType);
 			}
-			else if (type != null && type == DOMRegionContext.XML_TAG_OPEN) {
+			else if (type != null && (type == DOMRegionContext.XML_TAG_OPEN || type == DOMRegionContext.XML_END_TAG_OPEN)) {
 				translateXMLNode(containerRegion, regions);
 			}
 		}
@@ -854,6 +963,9 @@
 	}
 
 	private void handleScopingIfNecessary(ITextRegionCollection containerRegion) {
+		if (true)
+			return;
+
 		// code within a custom tag gets its own scope
 		// so if we encounter a start of a custom tag, we add '{'
 		// and for the end of a custom tag we add '}'
@@ -999,9 +1111,10 @@
 			if (r.getType() == DOMRegionContext.XML_TAG_NAME || r.getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME)
 
 			{
-				String fullTagName = container.getFullText(r).trim();
+				String fullTagName = container.getText(r);
 				if (fullTagName.indexOf(':') > -1) {
-					addTaglibVariables(fullTagName); // it may be a taglib
+					addTaglibVariables(fullTagName); // it may be a custom
+					// tag
 				}
 				StringTokenizer st = new StringTokenizer(fullTagName, ":.", false); //$NON-NLS-1$
 				if (st.hasMoreTokens() && st.nextToken().equals("jsp")) //$NON-NLS-1$
@@ -1099,30 +1212,37 @@
 		// tag name is not jsp
 		// handle embedded jsp attributes...
 		ITextRegion embedded = null;
-		//Iterator attrRegions = null;
-		//ITextRegion attrChunk = null;
+		// Iterator attrRegions = null;
+		// ITextRegion attrChunk = null;
 		while (regions.hasNext()) {
 			embedded = (ITextRegion) regions.next();
 			if (embedded instanceof ITextRegionContainer) {
 				// parse out container
-				
+
 				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=130606
 				// fix exponential iteration problem w/ embedded expressions
 				translateEmbeddedJSPInAttribute((ITextRegionContainer) embedded);
-//				attrRegions = ((ITextRegionContainer) embedded).getRegions().iterator();
-//				while (attrRegions.hasNext()) {
-//					attrChunk = (ITextRegion) attrRegions.next();
-//					String type = attrChunk.getType();
-//					// embedded JSP in attribute support only want to
-//					// translate one time per
-//					// embedded region so we only translate on the JSP open
-//					// tags (not content)
-//					if (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN || type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN || type == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || type == DOMJSPRegionContexts.JSP_EL_OPEN) {
-//						// now call jsptranslate
-//						translateEmbeddedJSPInAttribute((ITextRegionContainer) embedded);
-//						break;
-//					}
-//				}
+				// attrRegions = ((ITextRegionContainer)
+				// embedded).getRegions().iterator();
+				// while (attrRegions.hasNext()) {
+				// attrChunk = (ITextRegion) attrRegions.next();
+				// String type = attrChunk.getType();
+				// // embedded JSP in attribute support only want to
+				// // translate one time per
+				// // embedded region so we only translate on the JSP open
+				// // tags (not content)
+				// if (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN ||
+				// type ==
+				// DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || type ==
+				// DOMJSPRegionContexts.JSP_DECLARATION_OPEN || type ==
+				// DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || type ==
+				// DOMJSPRegionContexts.JSP_EL_OPEN) {
+				// // now call jsptranslate
+				// translateEmbeddedJSPInAttribute((ITextRegionContainer)
+				// embedded);
+				// break;
+				// }
+				// }
 			}
 		}
 	}
@@ -1649,7 +1769,10 @@
 			if (!getIncludes().contains(fileLocation) && getBaseLocation() != null && !fileLocation.equals(getBaseLocation())) {
 				getIncludes().push(fileLocation);
 				JSPIncludeRegionHelper helper = new JSPIncludeRegionHelper(this);
-				helper.parse(fileLocation);
+				boolean parsed = helper.parse(fileLocation);
+				if (!parsed) {
+					Logger.log(Logger.ERROR_DEBUG, "Error: included file " + filename + " not found {" + getBaseLocation() + ")");
+				}
 				getIncludes().pop();
 			}
 		}
@@ -1819,7 +1942,7 @@
 			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=128490
 			// length of 11 is the length of jsp:useBean
 			// and saves the expensive getText.equals call
-			if(r.getType() == DOMRegionContext.XML_TAG_NAME) {
+			if (r.getType() == DOMRegionContext.XML_TAG_NAME) {
 				if (r.getTextLength() == 11 && jspReferenceRegion.getText(r).equals("jsp:useBean")) { //$NON-NLS-1$
 					isUseBean = true;
 				}
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/XMLJSPRegionHelper.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/XMLJSPRegionHelper.java
index 583ec8c..0f1b416 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/XMLJSPRegionHelper.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/java/XMLJSPRegionHelper.java
@@ -87,17 +87,24 @@
 
 	/*
 	 * parse an entire file
+	 * 
+	 * @param filename
+	 * @return
 	 */
-	public void parse(String filename) {
+	public boolean parse(String filename) {
 		// from outer class
 		List blockMarkers = this.fTranslator.getBlockMarkers();
-		reset(getContents(filename));
+		String contents = getContents(filename);
+		if(contents == null)
+			return false;
+		reset(contents);
 		// this adds the current markers from the outer class list
 		// to this parser so parsing works correctly
 		for (int i = 0; i < blockMarkers.size(); i++) {
 			addBlockMarker((BlockMarker) blockMarkers.get(i));
 		}
 		forceParse();
+		return true;
 	}
 
 
@@ -167,7 +174,10 @@
 		}
 	}
 
+
 	private void handleScopingIfNecessary(IStructuredDocumentRegion sdRegion) {
+		if(true)
+			return;
 
 		// fix to make sure custom tag block have their own scope
 		// we add '{' for custom tag open and '}' for custom tag close
@@ -441,8 +451,11 @@
 		return nameStr.trim();
 	}
 
-	/*
+	/**
 	 * get the contents of a file as a String
+	 * 
+	 * @param fileName
+	 * @return the contents, null if the file could not be found
 	 */
 	protected String getContents(String fileName) {
 		StringBuffer s = new StringBuffer();
@@ -462,6 +475,10 @@
     				s.append((char) c);
     			}
             }
+            else {
+            	// error condition, file could not be found
+            	return null;
+            }
 		}
 		catch (Exception e) {
 			if (Debug.debugStructuredDocument)
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibClassLoader.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibClassLoader.java
index 2df3104..9823b3c 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibClassLoader.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibClassLoader.java
@@ -7,8 +7,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
@@ -37,7 +39,7 @@
 	private List usedJars = new ArrayList();
 	private List usedDirs = new ArrayList();
 
-	private List failedClasses = new ArrayList(); // CL: added to optimize
+	private Map failedClasses = new HashMap(); // CL: added to optimize
 													// failed loading
 
 	// private List loadedClassFilenames = new ArrayList();
@@ -58,7 +60,7 @@
 		// don't add the same entry twice, or search times will get even worse
 		if (!jarsList.contains(filename)) {
 			jarsList.add(filename);
-			failedClasses = new ArrayList();
+			failedClasses = new HashMap();
 			if (DEBUG)
 				System.out.println(" + [" + filename + "] added to classpath"); //$NON-NLS-1$ //$NON-NLS-2$
 		}
@@ -72,7 +74,7 @@
 	 */
 	public void removeJar(String filename) {
 		jarsList.remove(filename);
-		failedClasses = new ArrayList();
+		failedClasses = new HashMap();
 		if (DEBUG)
 			System.out.println("removed: [" + filename + "] from classpath"); //$NON-NLS-1$ //$NON-NLS-2$
 	}
@@ -80,7 +82,7 @@
 	public void addDirectory(String dirPath) {
 		if (!dirsList.contains(dirPath)) {
 			dirsList.add(dirPath);
-			failedClasses = new ArrayList();
+			failedClasses = new HashMap();
 			if (DEBUG)
 				System.out.println("added: [" + dirPath + "] to classpath"); //$NON-NLS-1$ //$NON-NLS-2$
 		}
@@ -94,7 +96,7 @@
 	 */
 	public void removeDirectory(String dirPath) {
 		dirsList.remove(dirPath);
-		failedClasses = new ArrayList();
+		failedClasses = new HashMap();
 		if (DEBUG)
 			System.out.println("removed: [" + dirPath + "] from classpath"); //$NON-NLS-1$ //$NON-NLS-2$
 	}
@@ -119,6 +121,10 @@
 	public List getDirectoriesInUse() {
 		return usedDirs;
 	}
+	
+	Map getFailures() {
+		return failedClasses;
+	}
 
 	/**
 	 * Returns a list of filenames for loose classes that have been loaded out
@@ -145,7 +151,7 @@
 				System.out.println(">> TaglibClassLoader " + this + " returning existing class: " + className); //$NON-NLS-1$ //$NON-NLS-2$
 			return oldClass;
 		}
-		if (failedClasses.contains(className)) {
+		if (failedClasses.containsKey(className)) {
 			if (DEBUG)
 				System.out.println(">> TaglibClassLoader " + this + " known missing class: " + className); //$NON-NLS-1$ //$NON-NLS-2$
 			throw new ClassNotFoundException();
@@ -186,8 +192,12 @@
 			if (stream != null) {
 				// found a class from a directory
 				ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+				byte[] buffer = new byte[2048];
 				while (stream.available() > 0) {
-					byteStream.write(stream.read());
+					int amountRead = stream.read(buffer);
+					if(amountRead > 0) {
+						byteStream.write(buffer, 0, amountRead);
+					}
 				}
 
 				byte[] byteArray = byteStream.toByteArray();
@@ -198,6 +208,7 @@
 					resolveClass(newClass);
 				}
 				catch (Throwable t) {
+					Logger.logException("Error loading TEI class " + className, t);
 
 					// j9 can give ClassCircularityError
 					// parent should already have the class then
@@ -286,6 +297,7 @@
 						resolveClass(newClass);
 					}
 					catch (Throwable t) {
+						Logger.logException("Error loading TEI class " + className, t);
 						// j9 can give ClassCircularityError
 						// parent should already have the class then
 
@@ -299,6 +311,7 @@
 						catch (ClassNotFoundException cnf) {
 							if (DEBUG)
 								cnf.printStackTrace();
+							failedClasses.put(className, cnf);
 						}
 					}
 					stream.close();
@@ -307,7 +320,7 @@
 			}
 		}
 		catch (Throwable t) {
-			failedClasses.add(className);
+			failedClasses.put(className, t);
 			return null;
 		}
 		finally {
@@ -331,7 +344,7 @@
 			return newClass;
 		}
 
-		failedClasses.add(className);
+//		failedClasses.add(className);
 		throw new ClassNotFoundException();
 	}
 
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibHelper.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibHelper.java
index 2315f4c..6f30f74 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibHelper.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibHelper.java
@@ -171,7 +171,7 @@
 				if (var.getVariableClass() != null) {
 					varClass = var.getVariableClass();
 				}
-				results.add(new TaglibVariable(varClass, varName));
+				results.add(new TaglibVariable(varClass, varName, var.getScope()));
 			}
 		}
 	}
@@ -196,7 +196,7 @@
 		if (teiClassname == null || teiClassname.length() == 0)
 			return;
 
-		ClassLoader loader = getClassloader();
+		TaglibClassLoader loader = getClassloader();
 
 		Class teiClass = null;
 		try {
@@ -216,7 +216,7 @@
 							VariableInfo[] vInfos = tei.getVariableInfo(td);
 							if (vInfos != null) {
 								for (int i = 0; i < vInfos.length; i++) {
-									results.add(new TaglibVariable(vInfos[i].getClassName(), vInfos[i].getVarName()));
+									results.add(new TaglibVariable(vInfos[i].getClassName(), vInfos[i].getVarName(), vInfos[i].getScope()));
 								}
 							}
 						}
@@ -344,7 +344,7 @@
 		return tagDataTable;
 	}
 
-	private ClassLoader getClassloader() {
+	private TaglibClassLoader getClassloader() {
 
 		if (fLoader == null) {
 			fLoader = new TaglibClassLoader(this.getClass().getClassLoader());
diff --git a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibVariable.java b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibVariable.java
index 0e0cecc..b43d6dd 100644
--- a/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibVariable.java
+++ b/bundles/org.eclipse.jst.jsp.core/src/org/eclipse/jst/jsp/core/internal/taglib/TaglibVariable.java
@@ -1,5 +1,7 @@
 package org.eclipse.jst.jsp.core.internal.taglib;
 
+import javax.servlet.jsp.tagext.VariableInfo;
+
 
 /**
  * Contains info about a TaglibVariable: classname, variablename.
@@ -9,13 +11,25 @@
     
 	private String fVarClass = null;
 	private String fVarName = null;
+	private int fScope;
 	private final String ENDL = "\n"; //$NON-NLS-1$
+
+	private final static String AT_END = "AT_END";
+	private final static String AT_BEGIN = "AT_BEGIN";
+	private final static String NESTED = "NESTED";
 	/**
 	 * 
 	 */
-	public TaglibVariable(String varClass, String varName) {
+	public TaglibVariable(String varClass, String varName, int scope) {
 		setVarClass(varClass);
 		setVarName(varName);
+		setScope(scope);
+	}
+
+	public TaglibVariable(String varClass, String varName, String scope) {
+		setVarClass(varClass);
+		setVarName(varName);
+		setScope(scope);
 	}
 	/**
 	 * @return Returns the fVarClass.
@@ -49,4 +63,26 @@
 	public final String getDeclarationString() {
 		return getVarClass() + " " + getVarName() + " = null;" + ENDL; //$NON-NLS-1$ //$NON-NLS-2$
 	}
+	public int getScope() {
+		return fScope;
+	}
+	public void setScope(int scope) {
+		fScope = scope;
+	}	
+	public void setScope(String scopeString) {
+		int scope = VariableInfo.AT_BEGIN;
+		
+		String trimmedScope = scopeString.trim();
+		if (NESTED.equals(trimmedScope)) {
+			scope = VariableInfo.NESTED;
+		}
+		else if (AT_BEGIN.equals(trimmedScope)) {
+			scope = VariableInfo.AT_BEGIN;
+		}
+		else if (AT_END.equals(trimmedScope)) {
+			scope = VariableInfo.AT_END;
+		}
+
+		fScope = scope;
+	}
 }
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 a58ad95..8d2aabe 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
@@ -25,11 +25,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
-import java.util.Stack;
+import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.core.filebuffers.FileBuffers;
 import org.eclipse.core.filebuffers.ITextFileBuffer;
@@ -69,6 +72,7 @@
 import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
 import org.eclipse.wst.xml.core.internal.catalog.provisional.ICatalog;
 import org.eclipse.wst.xml.core.internal.catalog.provisional.ICatalogEntry;
+import org.eclipse.wst.xml.core.internal.catalog.provisional.INextCatalog;
 import org.w3c.dom.Document;
 import org.w3c.dom.EntityReference;
 import org.w3c.dom.Node;
@@ -147,6 +151,7 @@
 		TaglibInfo info;
 		IPath location;
 		List urlRecords;
+		boolean isExported = true;
 
 		public boolean equals(Object obj) {
 			if (!(obj instanceof JarRecord))
@@ -257,7 +262,7 @@
 		}
 
 		public String toString() {
-			return "TaglibInfo|" + shortName + "|" + tlibVersion + "|" + smallIcon + "|" + largeIcon + "|" + jspVersion + "|" + uri + "|" + description;
+			return "TaglibInfo|" + shortName + "|" + tlibVersion + "|" + smallIcon + "|" + largeIcon + "|" + jspVersion + "|" + uri + "|" + description; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
 		}
 	}
 
@@ -340,6 +345,7 @@
 		String baseLocation;
 		TaglibInfo info;
 		URL url;
+		boolean isExported = true;
 
 		public URLRecord() {
 			super();
@@ -433,8 +439,9 @@
 
 	private static final String WEB_INF = "WEB-INF"; //$NON-NLS-1$
 	private static final IPath WEB_INF_PATH = new Path(WEB_INF);
+	private static final String BUILDPATH_PROJECT = "BUILDPATH_PROJECT"; //$NON-NLS-1$
 	private static final String WEB_XML = "web.xml"; //$NON-NLS-1$
-	private static final String SAVE_FORMAT_VERSION = "1.0.1";
+	private static final String SAVE_FORMAT_VERSION = "Tag Library Index 1.0.1"; //$NON-NLS-1$
 	private static final String BUILDPATH_DIRTY = "BUILDPATH_DIRTY"; //$NON-NLS-1$
 
 	/*
@@ -449,7 +456,12 @@
 	 */
 	boolean fBuildPathIsDirty = false;
 
-	Stack fClasspathProjects = null;
+	/**
+	 * A set of the projects that are in this project's build path.
+	 * Lookups/enumerations will be redirected to the corresponding
+	 * ProjectDescription instances
+	 */
+	Set fClasspathProjects = null;
 
 	// holds references by URI to JARs
 	Hashtable fClasspathReferences;
@@ -485,6 +497,7 @@
 		fWebXMLReferences = new Hashtable(0);
 		fImplicitReferences = new Hashtable(0);
 		fClasspathReferences = new Hashtable(0);
+		fClasspathProjects = new HashSet();
 
 		restoreReferences();
 	}
@@ -502,6 +515,48 @@
 	}
 
 	/**
+	 * Adds the list of known references from this project's build path to the
+	 * map, appending any processed projects into the list to avoid
+	 * build-path-cycles.
+	 * 
+	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=142408
+	 * 
+	 * @param references -
+	 *            the map of references to ITaglibRecords
+	 * @param projectsProcessed -
+	 *            the list of projects already considered
+	 * @param exportedOnly -
+	 *            Whether to only add references derived from exported build
+	 *            path containers. This method calls itself recursively with
+	 *            this parameter as false.
+	 */
+	void addBuildPathReferences(Map references, List projectsProcessed, boolean exportedOnly) {
+		ensureUpTodate();
+
+		Enumeration keys = fClasspathReferences.keys();
+		while (keys.hasMoreElements()) {
+			Object key = keys.nextElement();
+			URLRecord urlRecord = (URLRecord) fClasspathReferences.get(key);
+			if (exportedOnly) {
+				if (urlRecord.isExported) {
+					references.put(key, urlRecord);
+				}
+			}
+			else {
+				references.put(key, urlRecord);
+			}
+		}
+		IProject[] buildpathProjects = (IProject[]) fClasspathProjects.toArray(new IProject[fClasspathProjects.size()]);
+		for (int i = 0; i < buildpathProjects.length; i++) {
+			if (!projectsProcessed.contains(buildpathProjects[i]) && buildpathProjects[i].isAccessible()) {
+				projectsProcessed.add(buildpathProjects[i]);
+				ProjectDescription description = TaglibIndex._instance.createDescription(buildpathProjects[i]);
+				description.addBuildPathReferences(references, projectsProcessed, true);
+			}
+		}
+	}
+
+	/**
 	 * Erases all known tables
 	 */
 	void clear() {
@@ -741,21 +796,44 @@
 	synchronized List getAvailableTaglibRecords(IPath path) {
 		ensureUpTodate();
 
-		Collection implicitReferences = getImplicitReferences(path.toString()).values();
+		Collection implicitReferences = new HashSet(getImplicitReferences(path.toString()).values());
 		List records = new ArrayList(fTLDReferences.size() + fTagDirReferences.size() + fJARReferences.size() + fWebXMLReferences.size());
 		records.addAll(fTLDReferences.values());
 		records.addAll(fTagDirReferences.values());
 		records.addAll(_getJSP11AndWebXMLJarReferences(fJARReferences.values()));
-		records.addAll(fClasspathReferences.values());
 		records.addAll(implicitReferences);
 
-		ICatalog catalog = XMLCorePlugin.getDefault().getDefaultXMLCatalog();
-		if (catalog != null) {
-			ICatalogEntry[] entries = catalog.getCatalogEntries();
-			for (int i = 0; i < entries.length; i++) {
-				ITaglibRecord record = createCatalogRecord(entries[i].getURI());
+		Map buildPathReferences = new HashMap();
+		List projectsProcessed = new ArrayList(fClasspathProjects.size() + 1);
+		projectsProcessed.add(fProject);
+		addBuildPathReferences(buildPathReferences, projectsProcessed, false);
+		records.addAll(buildPathReferences.values());
+
+		ICatalog defaultCatalog = XMLCorePlugin.getDefault().getDefaultXMLCatalog();
+		if (defaultCatalog != null) {
+			// Process default catalog
+			ICatalogEntry[] entries = defaultCatalog.getCatalogEntries();
+			for (int entry = 0; entry < entries.length; entry++) {
+				ITaglibRecord record = createCatalogRecord(entries[entry].getURI());
 				records.add(record);
 			}
+
+			// Process declared OASIS nextCatalogs catalog
+			INextCatalog[] nextCatalogs = defaultCatalog.getNextCatalogs();
+			for (int nextCatalog = 0; nextCatalog < nextCatalogs.length; nextCatalog++) {
+				ICatalog catalog = nextCatalogs[nextCatalog].getReferencedCatalog();
+				ICatalogEntry[] entries2 = catalog.getCatalogEntries();
+				for (int entry = 0; entry < entries2.length; entry++) {
+					String uri = entries2[entry].getURI();
+					if (uri != null) {
+						uri = uri.toLowerCase(Locale.US);
+						if (uri.endsWith((".jar")) || uri.endsWith((".tld"))) {
+							ITaglibRecord record = createCatalogRecord(uri);
+							records.add(record);
+						}
+					}
+				}
+			}
 		}
 
 		return records;
@@ -913,7 +991,14 @@
 					libPath = element.getPath().toString();
 			}
 			if (libPath != null) {
-				updateClasspathLibrary(libPath, taglibRecordEventKind);
+				boolean fragmentisExported = true;
+				try {
+					fragmentisExported = ((IPackageFragmentRoot) element).getRawClasspathEntry().isExported();
+				}
+				catch (JavaModelException e) {
+					Logger.logException("Problem handling build path entry for " + element.getPath(), e); //$NON-NLS-1$
+				}
+				updateClasspathLibrary(libPath, taglibRecordEventKind, fragmentisExported);
 			}
 			if (_debugIndexTime)
 				Logger.log(Logger.INFO, "processed build path delta for " + fProject.getName() + "(" + element.getPath() + ") in " + (System.currentTimeMillis() - time0) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
@@ -940,14 +1025,24 @@
 	}
 
 	void indexClasspath() {
-		time0 = System.currentTimeMillis();
-		fClasspathProjects = new Stack();
-
+		if (_debugIndexTime)
+			time0 = System.currentTimeMillis();
+		fClasspathProjects.clear();
 		fClasspathReferences.clear();
 		fClasspathJars.clear();
 
 		IJavaProject javaProject = JavaCore.create(fProject);
-		indexClasspath(javaProject);
+		/*
+		 * If the Java nature isn't present (or something else is wrong),
+		 * don't check the build path.
+		 */
+		if (javaProject.exists()) {
+			indexClasspath(javaProject);
+		}
+		// else {
+		// Logger.log(Logger.WARNING, "TaglibIndex was asked to index non-Java
+		// Project " + fProject.getName()); //$NON-NLS-1$
+		// }
 
 		if (_debugIndexTime)
 			Logger.log(Logger.INFO, "indexed " + fProject.getName() + " classpath in " + (System.currentTimeMillis() - time0) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
@@ -974,7 +1069,7 @@
 					IPath libPath = entry.getPath();
 					if (!fClasspathJars.containsKey(libPath.toString())) {
 						if (libPath.toFile().exists()) {
-							updateClasspathLibrary(libPath.toString(), ITaglibRecordEvent.ADDED);
+							updateClasspathLibrary(libPath.toString(), ITaglibRecordEvent.ADDED, entry.isExported());
 						}
 						else {
 							/*
@@ -985,7 +1080,7 @@
 							 */
 							IFile libFile = ResourcesPlugin.getWorkspace().getRoot().getFile(libPath);
 							if (libFile != null && libFile.exists()) {
-								updateClasspathLibrary(libFile.getLocation().toString(), ITaglibRecordEvent.ADDED);
+								updateClasspathLibrary(libFile.getLocation().toString(), ITaglibRecordEvent.ADDED, entry.isExported());
 							}
 						}
 					}
@@ -994,14 +1089,12 @@
 				break;
 			case IClasspathEntry.CPE_PROJECT : {
 				/*
-				 * Ignore required projects of required projects that are not
-				 * exported
+				 * We're currently ignoring whether the project exports all of
+				 * its build path
 				 */
-				if (fClasspathProjects.size() < 2 || entry.isExported()) {
-					IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(entry.getPath().lastSegment());
-					if (project != null && !fClasspathProjects.contains(project.getName())) {
-						indexClasspath(JavaCore.create(project));
-					}
+				IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(entry.getPath().lastSegment());
+				if (project != null) {
+					fClasspathProjects.add(project);
 				}
 			}
 				break;
@@ -1027,8 +1120,11 @@
 	 * @param javaProject
 	 */
 	private void indexClasspath(IJavaProject javaProject) {
-		if (javaProject != null && javaProject.exists() && !fClasspathProjects.contains(javaProject.getElementName())) {
-			fClasspathProjects.push(javaProject.getElementName());
+		if (javaProject == null)
+			return;
+
+		IProject project = javaProject.getProject();
+		if (project.equals(fProject)) {
 			try {
 				IClasspathEntry[] entries = javaProject.getResolvedClasspath(true);
 				for (int i = 0; i < entries.length; i++) {
@@ -1038,7 +1134,6 @@
 			catch (JavaModelException e) {
 				Logger.logException("Error searching Java Build Path + (" + fProject.getName() + ") for tag libraries", e); //$NON-NLS-1$ //$NON-NLS-2$
 			}
-			fClasspathProjects.pop();
 		}
 	}
 
@@ -1149,6 +1244,13 @@
 		if (record == null) {
 			record = (ITaglibRecord) fClasspathReferences.get(reference);
 		}
+		if (record == null) {
+			Map buildPathReferences = new HashMap();
+			List projectsProcessed = new ArrayList(fClasspathProjects.size() + 1);
+			projectsProcessed.add(fProject);
+			addBuildPathReferences(buildPathReferences, projectsProcessed, false);
+			record = (ITaglibRecord) buildPathReferences.get(reference);
+		}
 
 		// Check the XML Catalog
 		if (record == null) {
@@ -1199,7 +1301,7 @@
 			// resources first
 			index();
 			// now build path
-			
+
 			// ================ test reload time ========================
 			boolean restored = false;
 			File savedState = new File(fSaveStateFilename);
@@ -1217,18 +1319,21 @@
 						String lineText = doc.get(line.getOffset(), line.getLength());
 						JarRecord libraryRecord = null;
 						if (SAVE_FORMAT_VERSION.equals(lineText)) {
+							IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+
 							for (int i = 1; i < lines; i++) {
 								line = doc.getLineInformation(i);
 								lineText = doc.get(line.getOffset(), line.getLength());
-								StringTokenizer toker = new StringTokenizer(lineText, "|");
+								StringTokenizer toker = new StringTokenizer(lineText, "|"); //$NON-NLS-1$
 								if (toker.hasMoreTokens()) {
 									String tokenType = toker.nextToken();
-									if ("JAR".equalsIgnoreCase(tokenType)) {
+									if ("JAR".equalsIgnoreCase(tokenType)) { //$NON-NLS-1$ //$NON-NLS-2$
 										boolean has11TLD = Boolean.valueOf(toker.nextToken()).booleanValue();
+										boolean exported = Boolean.valueOf(toker.nextToken()).booleanValue();
 										// make the rest the libraryLocation
 										String libraryLocation = toker.nextToken();
 										while (toker.hasMoreTokens()) {
-											libraryLocation = libraryLocation + "|" + toker.nextToken();
+											libraryLocation = libraryLocation + "|" + toker.nextToken(); //$NON-NLS-1$ //$NON-NLS-2$
 										}
 										libraryLocation = libraryLocation.trim();
 										if (libraryRecord != null) {
@@ -1237,6 +1342,7 @@
 										// Create a new JarRecord
 										libraryRecord = createJARRecord(libraryLocation);
 										libraryRecord.has11TLD = has11TLD;
+										libraryRecord.isExported = exported;
 
 										// Add a URLRecord for the 1.1 TLD
 										if (has11TLD) {
@@ -1245,19 +1351,22 @@
 												TaglibInfo info = extractInfo(libraryLocation, contents);
 
 												if (info != null && info.uri != null && info.uri.length() > 0) {
-													URLRecord record = new URLRecord();
-													record.info = info;
-													record.baseLocation = libraryLocation;
+													URLRecord urlRecord = new URLRecord();
+													urlRecord.info = info;
+													urlRecord.isExported = exported;
+													urlRecord.baseLocation = libraryLocation;
 													try {
-														record.url = new URL("jar:file:" + libraryLocation + "!/" + JarUtilities.JSP11_TAGLIB); //$NON-NLS-1$ //$NON-NLS-2$
-														libraryRecord.urlRecords.add(record);
-														fClasspathReferences.put(record.getURI(), record);
+														urlRecord.url = new URL("jar:file:" + libraryLocation + "!/" + JarUtilities.JSP11_TAGLIB); //$NON-NLS-1$ //$NON-NLS-2$
+														libraryRecord.urlRecords.add(urlRecord);
+														fClasspathReferences.put(urlRecord.getURI(), urlRecord);
 														if (_debugIndexCreation)
-															Logger.log(Logger.INFO, "created record for " + record.getURI() + "@" + record.getURL()); //$NON-NLS-1$ //$NON-NLS-2$
+															Logger.log(Logger.INFO, "created record for " + urlRecord.getURI() + "@" + urlRecord.getURL()); //$NON-NLS-1$ //$NON-NLS-2$
 													}
 													catch (MalformedURLException e) {
-														// don't record this
-														// URI
+														/*
+														 * don't record this
+														 * URI
+														 */
 														Logger.logException(e);
 													}
 												}
@@ -1271,16 +1380,18 @@
 
 										fClasspathJars.put(libraryLocation, libraryRecord);
 									}
-									else if ("URL".equalsIgnoreCase(tokenType)) {
+									else if ("URL".equalsIgnoreCase(tokenType)) { //$NON-NLS-1$
+										boolean exported = Boolean.valueOf(toker.nextToken()).booleanValue();
 										// make the rest the URL
 										String urlString = toker.nextToken();
 										while (toker.hasMoreTokens()) {
-											urlString = urlString + "|" + toker.nextToken();
+											urlString = urlString + "|" + toker.nextToken(); //$NON-NLS-1$ //$NON-NLS-2$
 										}
 										urlString = urlString.trim();
 										// Append a URLrecord
 										URLRecord urlRecord = new URLRecord();
 										urlRecord.url = new URL(urlString); //$NON-NLS-1$ //$NON-NLS-2$
+										urlRecord.isExported = exported;
 										urlRecord.baseLocation = libraryRecord.location.toString();
 										libraryRecord.urlRecords.add(urlRecord);
 
@@ -1327,25 +1438,35 @@
 										}
 										fClasspathReferences.put(urlRecord.getURI(), urlRecord);
 									}
+									else if (BUILDPATH_PROJECT.equalsIgnoreCase(tokenType)) {
+										String projectName = toker.nextToken();
+										if (Path.ROOT.isValidSegment(projectName)) {
+											IProject project = workspaceRoot.getProject(projectName);
+											/* do not check if "open" here */
+											if (project != null) {
+												fClasspathProjects.add(project);
+											}
+										}
+									}
 									// last since only occurs once
 									else if (BUILDPATH_DIRTY.equalsIgnoreCase(tokenType)) {
 										fBuildPathIsDirty = Boolean.valueOf(toker.nextToken()).booleanValue();
 									}
 								}
+								if (libraryRecord != null) {
+									TaglibIndex.fireTaglibRecordEvent(new TaglibRecordEvent(libraryRecord, ITaglibRecordEvent.ADDED));
+								}
 							}
-							if (libraryRecord != null) {
-								TaglibIndex.fireTaglibRecordEvent(new TaglibRecordEvent(libraryRecord, ITaglibRecordEvent.ADDED));
-							}
+							restored = true;
 						}
 					}
-					restored = true;
 					if (_debugIndexTime)
-						Logger.log(Logger.INFO, "time spent reloading " + fProject.getName() + " build path: " + (System.currentTimeMillis() - time0));
+						Logger.log(Logger.INFO, "time spent reloading " + fProject.getName() + " build path: " + (System.currentTimeMillis() - time0)); //$NON-NLS-1$ //$NON-NLS-2$
 				}
 				catch (Exception e) {
 					restored = false;
 					if (_debugIndexTime)
-						Logger.log(Logger.INFO, "failure reloading " + fProject.getName() + " build path index", e);
+						Logger.log(Logger.INFO, "failure reloading " + fProject.getName() + " build path index", e); //$NON-NLS-1$ //$NON-NLS-2$
 				}
 				finally {
 					try {
@@ -1377,34 +1498,46 @@
 
 		/**
 		 * <pre>
-		 *                                        1.0
-		 *                                        Save classpath information (| is field delimiter)
-		 *                                        Jars are saved as &quot;JAR:&quot;+ has11TLD + jar path 
-		 *                                        URLRecords as &quot;URL:&quot;+URL
+		 *             		 1.0.1
+		 *             		 Save classpath information (| is field delimiter)
+		 *             		 Jars are saved as &quot;JAR:&quot;+ has11TLD + jar path 
+		 *             		 URLRecords as &quot;URL:&quot;+URL
 		 * </pre>
 		 */
 		try {
-			writer = new OutputStreamWriter(new FileOutputStream(fSaveStateFilename), "utf8");
+			writer = new OutputStreamWriter(new FileOutputStream(fSaveStateFilename), "utf16"); //$NON-NLS-1$
 			writer.write(SAVE_FORMAT_VERSION);
-			writer.write('\n');
-			writer.write(BUILDPATH_DIRTY + "|" + fBuildPathIsDirty);
-			writer.write('\n');
+			writer.write('\n'); //$NON-NLS-1$
+			writer.write(BUILDPATH_DIRTY + "|" + fBuildPathIsDirty); //$NON-NLS-1$
+			writer.write('\n'); //$NON-NLS-1$
+
+			IProject[] projects = (IProject[]) fClasspathProjects.toArray(new IProject[0]);
+			for (int i = 0; i < projects.length; i++) {
+				writer.write(BUILDPATH_PROJECT);
+				writer.write("|"); //$NON-NLS-1$
+				writer.write(projects[i].getName());
+				writer.write('\n'); //$NON-NLS-1$
+			}
 
 			Enumeration jars = fClasspathJars.keys();
 			while (jars.hasMoreElements()) {
 				String jarPath = jars.nextElement().toString();
 				JarRecord jarRecord = (JarRecord) fClasspathJars.get(jarPath);
-				writer.write("JAR|");
+				writer.write("JAR|"); //$NON-NLS-1$
 				writer.write(Boolean.toString(jarRecord.has11TLD));
-				writer.write('|');
+				writer.write('|'); //$NON-NLS-1$
+				writer.write(Boolean.toString(jarRecord.isExported));
+				writer.write('|'); //$NON-NLS-1$
 				writer.write(jarPath);
-				writer.write('\n');
+				writer.write('\n'); //$NON-NLS-1$
 				Iterator i = jarRecord.urlRecords.iterator();
 				while (i.hasNext()) {
-					IURLRecord urlRecord = (IURLRecord) i.next();
-					writer.write("URL|");
+					URLRecord urlRecord = (URLRecord) i.next();
+					writer.write("URL|"); //$NON-NLS-1$
+					writer.write(String.valueOf(urlRecord.isExported));
+					writer.write("|"); //$NON-NLS-1$
 					writer.write(urlRecord.getURL().toExternalForm());
-					writer.write('\n');
+					writer.write('\n'); //$NON-NLS-1$
 				}
 			}
 		}
@@ -1421,14 +1554,14 @@
 		}
 
 		if (_debugIndexTime)
-			Logger.log(Logger.INFO, "time spent saving index for " + fProject.getName() + ": " + (System.currentTimeMillis() - time0));
+			Logger.log(Logger.INFO, "time spent saving index for " + fProject.getName() + ": " + (System.currentTimeMillis() - time0)); //$NON-NLS-1$
 	}
-	
+
 	void setBuildPathIsDirty() {
 		fBuildPathIsDirty = true;
 	}
 
-	void updateClasspathLibrary(String libraryLocation, int deltaKind) {
+	void updateClasspathLibrary(String libraryLocation, int deltaKind, boolean isExported) {
 		JarRecord libraryRecord = null;
 		if (deltaKind == ITaglibRecordEvent.REMOVED || deltaKind == ITaglibRecordEvent.CHANGED) {
 			libraryRecord = (JarRecord) fClasspathJars.remove(libraryLocation);
@@ -1443,6 +1576,7 @@
 			String[] entries = JarUtilities.getEntryNames(libraryLocation);
 
 			libraryRecord = createJARRecord(libraryLocation);
+			libraryRecord.isExported = isExported;
 			fClasspathJars.put(libraryLocation, libraryRecord);
 			for (int i = 0; i < entries.length; i++) {
 				if (entries[i].endsWith(".tld")) { //$NON-NLS-1$
@@ -1454,15 +1588,16 @@
 						TaglibInfo info = extractInfo(libraryLocation, contents);
 
 						if (info != null && info.uri != null && info.uri.length() > 0) {
-							URLRecord record = new URLRecord();
-							record.info = info;
-							record.baseLocation = libraryLocation;
+							URLRecord urlRecord = new URLRecord();
+							urlRecord.info = info;
+							urlRecord.baseLocation = libraryLocation;
 							try {
-								record.url = new URL("jar:file:" + libraryLocation + "!/" + entries[i]); //$NON-NLS-1$ //$NON-NLS-2$
-								libraryRecord.urlRecords.add(record);
-								fClasspathReferences.put(record.getURI(), record);
+								urlRecord.isExported = isExported;
+								urlRecord.url = new URL("jar:file:" + libraryLocation + "!/" + entries[i]); //$NON-NLS-1$ //$NON-NLS-2$
+								libraryRecord.urlRecords.add(urlRecord);
+								fClasspathReferences.put(urlRecord.getURI(), urlRecord);
 								if (_debugIndexCreation)
-									Logger.log(Logger.INFO, "created record for " + record.getURI() + "@" + record.getURL()); //$NON-NLS-1$ //$NON-NLS-2$
+									Logger.log(Logger.INFO, "created record for " + urlRecord.getURI() + "@" + urlRecord.getURL()); //$NON-NLS-1$ //$NON-NLS-2$
 							}
 							catch (MalformedURLException e) {
 								// don't record this URI
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 f7b257e..02e52b7 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
@@ -18,7 +18,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 import java.util.zip.CRC32;
 
 import org.eclipse.core.filebuffers.FileBuffers;
@@ -45,7 +44,6 @@
 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.utils.StringUtils;
 import org.osgi.framework.Bundle;
 
 /**
@@ -63,7 +61,6 @@
  */
 public final class TaglibIndex {
 	class ClasspathChangeListener implements IElementChangedListener {
-		Stack classpathStack = new Stack();
 		List projectsIndexed = new ArrayList(1);
 
 		public void elementChanged(ElementChangedEvent event) {
@@ -71,16 +68,15 @@
 				return;
 			try {
 				LOCK.acquire();
-				classpathStack.clear();
 				projectsIndexed.clear();
-				elementChanged(event.getDelta());
+				elementChanged(event.getDelta(), true);
 			}
 			finally {
 				LOCK.release();
 			}
 		}
 
-		private void elementChanged(IJavaElementDelta delta) {
+		private void elementChanged(IJavaElementDelta delta, boolean forceUpdate) {
 			if (frameworkIsShuttingDown())
 				return;
 
@@ -88,19 +84,25 @@
 			if (element.getElementType() == IJavaElement.JAVA_MODEL) {
 				IJavaElementDelta[] changed = delta.getAffectedChildren();
 				for (int i = 0; i < changed.length; i++) {
-					elementChanged(changed[i]);
+					elementChanged(changed[i], forceUpdate);
 				}
 			}
 			// Handle any changes at the project level
 			else if (element.getElementType() == IJavaElement.JAVA_PROJECT) {
 				if ((delta.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0) {
 					IJavaElement proj = element;
-					handleClasspathChange((IJavaProject) proj);
+					handleClasspathChange((IJavaProject) proj, forceUpdate);
 				}
 				else {
 					IJavaElementDelta[] deltas = delta.getAffectedChildren();
-					for (int i = 0; i < deltas.length; i++) {
-						elementChanged(deltas[i]);
+					if (deltas.length == 0) {
+						IJavaElement proj = element;
+						handleClasspathChange((IJavaProject) proj, forceUpdate);
+					}
+					else {
+						for (int i = 0; i < deltas.length; i++) {
+							elementChanged(deltas[i], false);
+						}
 					}
 				}
 			}
@@ -126,51 +128,39 @@
 							affectedDescription.handleElementChanged(delta);
 						}
 					}
+					projectsIndexed.add(affectedProject.getProject());
 				}
 			}
 		}
 
-		private void handleClasspathChange(IJavaProject project) {
+		private void handleClasspathChange(IJavaProject project, boolean forceUpdate) {
 			if (frameworkIsShuttingDown())
 				return;
 
-			/*
-			 * Loops in the build paths could cause us to pop more than we
-			 * push
-			 */
-			if (classpathStack.contains(project.getElementName()))
-				return;
-
-			classpathStack.push(project.getElementName());
 			try {
 				/* Handle large changes to this project's build path */
 				IResource resource = project.getCorrespondingResource();
 				if (resource.getType() == IResource.PROJECT && !projectsIndexed.contains(resource)) {
-					ProjectDescription description = getDescription((IProject) resource);
+					/*
+					 * Use get instead of create since the downstream
+					 * (upstream?) project wasn't itself modified.
+					 */
+					ProjectDescription description = null;
+					if (forceUpdate) {
+						description = createDescription((IProject) resource);
+					}
+					else {
+						description = getDescription((IProject) resource);
+					}
 					if (description != null && !frameworkIsShuttingDown()) {
 						projectsIndexed.add(resource);
 						description.setBuildPathIsDirty();
 					}
 				}
-				/*
-				 * Update indeces for projects who include this project in
-				 * their build path (e.g. toggling the "exportation" of a
-				 * taglib JAR in this project affects the JAR's visibility in
-				 * other projects)
-				 */
-				IJavaProject[] projects = project.getJavaModel().getJavaProjects();
-				for (int i = 0; i < projects.length; i++) {
-					IJavaProject otherProject = projects[i];
-					if (StringUtils.contains(otherProject.getRequiredProjectNames(), project.getElementName(), false) && !classpathStack.contains(otherProject.getElementName()) && !frameworkIsShuttingDown()) {
-						handleClasspathChange(otherProject);
-					}
-				}
 			}
 			catch (JavaModelException e) {
 				Logger.logException(e);
 			}
-			classpathStack.pop();
-
 		}
 	}
 
@@ -324,7 +314,8 @@
 	private static final String CLEAN = "CLEAN";
 	private static final String DIRTY = "DIRTY";
 	static boolean ENABLED = false;
-	static ILock LOCK = null;
+
+	static ILock LOCK = Platform.getJobManager().newLock();
 
 	/**
 	 * NOT API.
@@ -335,7 +326,8 @@
 	public static void addTaglibIndexListener(ITaglibIndexListener listener) {
 		try {
 			LOCK.acquire();
-			_instance.internalAddTaglibIndexListener(listener);
+			if (_instance != null)
+				_instance.internalAddTaglibIndexListener(listener);
 		}
 		finally {
 			LOCK.release();
@@ -352,14 +344,16 @@
 		 */
 		TLDCMDocumentManager.getSharedDocumentCache().remove(TLDCMDocumentManager.getUniqueIdentifier(event.getTaglibRecord()));
 
-		ITaglibIndexListener[] listeners = _instance.fTaglibIndexListeners;
-		if (listeners != null) {
-			for (int i = 0; i < listeners.length; i++) {
-				try {
-					listeners[i].indexChanged(event);
-				}
-				catch (Exception e) {
-					Logger.log(Logger.WARNING, e.getMessage());
+		if (_instance != null) {
+			ITaglibIndexListener[] listeners = _instance.fTaglibIndexListeners;
+			if (listeners != null) {
+				for (int i = 0; i < listeners.length; i++) {
+					try {
+						listeners[i].indexChanged(event);
+					}
+					catch (Exception e) {
+						Logger.log(Logger.WARNING, e.getMessage());
+					}
 				}
 			}
 		}
@@ -378,8 +372,18 @@
 	 * @return All of the visible ITaglibRecords from the given path.
 	 */
 	public static ITaglibRecord[] getAvailableTaglibRecords(IPath fullPath) {
-		ITaglibRecord[] records = _instance.internalGetAvailableTaglibRecords(fullPath);
-		return records;
+		try {
+			LOCK.acquire();
+			ITaglibRecord[] records = null;
+			if (_instance != null)
+				records = _instance.internalGetAvailableTaglibRecords(fullPath);
+			else
+				records = new ITaglibRecord[0];
+			return records;
+		}
+		finally {
+			LOCK.release();
+		}
 	}
 
 	/**
@@ -417,7 +421,8 @@
 	public static void removeTaglibIndexListener(ITaglibIndexListener listener) {
 		try {
 			LOCK.acquire();
-			_instance.internalRemoveTaglibIndexListener(listener);
+			if (_instance != null)
+				_instance.internalRemoveTaglibIndexListener(listener);
 		}
 		finally {
 			LOCK.release();
@@ -446,7 +451,8 @@
 		ITaglibRecord result = null;
 		try {
 			LOCK.acquire();
-			result = _instance.internalResolve(basePath, reference, crossProjects);
+			if (_instance != null)
+				result = _instance.internalResolve(basePath, reference, crossProjects);
 		}
 		finally {
 			LOCK.release();
@@ -526,10 +532,16 @@
 	 * changes, and to forget all information about the workspace.
 	 */
 	public static synchronized void shutdown() {
-		if (_instance != null) {
-			_instance.stop();
+		try {
+			LOCK.acquire();
+			if (_instance != null) {
+				_instance.stop();
+			}
+			_instance = null;
 		}
-		_instance = null;
+		finally {
+			LOCK.release();
+		}
 	}
 
 	/**
@@ -537,8 +549,14 @@
 	 * changes.
 	 */
 	public static synchronized void startup() {
-		ENABLED = !"false".equalsIgnoreCase(System.getProperty(TaglibIndex.class.getName())); //$NON-NLS-1$
-		_instance = new TaglibIndex();
+		try {
+			LOCK.acquire();
+			ENABLED = !"false".equalsIgnoreCase(System.getProperty(TaglibIndex.class.getName())); //$NON-NLS-1$
+			_instance = new TaglibIndex();
+		}
+		finally {
+			LOCK.release();
+		}
 	}
 
 	private ClasspathChangeListener fClasspathChangeListener = null;
@@ -563,7 +581,6 @@
 			removeIndexes(false);
 		}
 
-		LOCK = Platform.getJobManager().newLock();
 		fProjectDescriptions = new Hashtable();
 		fResourceChangeListener = new ResourceChangeListener();
 		fClasspathChangeListener = new ClasspathChangeListener();
@@ -656,20 +673,22 @@
 			ProjectDescription description = createDescription(project);
 			List availableRecords = description.getAvailableTaglibRecords(path);
 
-//			ICatalog catalog = XMLCorePlugin.getDefault().getDefaultXMLCatalog();
-//			while (catalog != null) {
-//				ICatalogEntry[] entries = catalog.getCatalogEntries();
-//				for (int i = 0; i < entries.length; i++) {
-//					// System.out.println(entries[i].getURI());
-//				}
-//				INextCatalog[] nextCatalogs = catalog.getNextCatalogs();
-//				for (int i = 0; i < nextCatalogs.length; i++) {
-//					ICatalogEntry[] entries2 = nextCatalogs[i].getReferencedCatalog().getCatalogEntries();
-//					for (int j = 0; j < entries2.length; j++) {
-//						// System.out.println(entries2[j].getURI());
-//					}
-//				}
-//			}
+			// ICatalog catalog =
+			// XMLCorePlugin.getDefault().getDefaultXMLCatalog();
+			// while (catalog != null) {
+			// ICatalogEntry[] entries = catalog.getCatalogEntries();
+			// for (int i = 0; i < entries.length; i++) {
+			// // System.out.println(entries[i].getURI());
+			// }
+			// INextCatalog[] nextCatalogs = catalog.getNextCatalogs();
+			// for (int i = 0; i < nextCatalogs.length; i++) {
+			// ICatalogEntry[] entries2 =
+			// nextCatalogs[i].getReferencedCatalog().getCatalogEntries();
+			// for (int j = 0; j < entries2.length; j++) {
+			// // System.out.println(entries2[j].getURI());
+			// }
+			// }
+			// }
 
 			records = (ITaglibRecord[]) availableRecords.toArray(records);
 		}