/******************************************************************************* * Copyright (c) 2004, 2012 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.jst.jsp.core.internal.contentmodel.tld; import java.io.File; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Stack; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.jst.jsp.core.internal.Logger; import org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP11TLDNames; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP12TLDNames; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP20TLDNames; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDDocument; import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDElementDeclaration; import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache; import org.eclipse.jst.jsp.core.internal.contenttype.DeploymentDescriptorPropertyCache.PropertyGroup; import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace; import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts; import org.eclipse.jst.jsp.core.internal.util.FacetModuleCoreSupport; import org.eclipse.jst.jsp.core.internal.util.FileContentCache; import org.eclipse.jst.jsp.core.internal.util.ZeroStructuredDocumentRegion; import org.eclipse.jst.jsp.core.taglib.IJarRecord; import org.eclipse.jst.jsp.core.taglib.ITLDRecord; import org.eclipse.jst.jsp.core.taglib.ITagDirRecord; import org.eclipse.jst.jsp.core.taglib.ITaglibIndexDelta; import org.eclipse.jst.jsp.core.taglib.ITaglibIndexListener; import org.eclipse.jst.jsp.core.taglib.ITaglibRecord; import org.eclipse.jst.jsp.core.taglib.IURLRecord; import org.eclipse.jst.jsp.core.taglib.TaglibIndex; import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin; import org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker; import org.eclipse.wst.sse.core.internal.ltk.parser.JSPCapableParser; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandler; import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionHandlerExtension; import org.eclipse.wst.sse.core.internal.ltk.parser.TagMarker; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; import org.eclipse.wst.sse.core.internal.util.Assert; import org.eclipse.wst.sse.core.internal.util.Debug; import org.eclipse.wst.sse.core.utils.StringUtils; import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument; import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap; import org.eclipse.wst.xml.core.internal.parser.XMLSourceParser; import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; public class TLDCMDocumentManager implements ITaglibIndexListener { protected class DirectiveStructuredDocumentRegionHandler implements StructuredDocumentRegionHandler, StructuredDocumentRegionHandlerExtension { /** * Adds a block tagname (fully namespace qualified) into the list of * block tag names for the parser. The marker * IStructuredDocumentRegion along with position cues during reparses * allow the XMLSourceParser to enable/ignore the tags as blocks. */ protected void addBlockTag(String tagnameNS, ITextRegionCollection marker) { if (getParser() == null) return; if (getParser().getBlockMarker(tagnameNS) == null) { getParser().addBlockMarker(new BlockMarker(tagnameNS, marker, DOMRegionContext.BLOCK_TEXT, true, false)); if (_debug) { System.out.println("TLDCMDocumentManager added block marker: " + tagnameNS + "@" + marker.getStartOffset()); //$NON-NLS-2$//$NON-NLS-1$ } } } protected void addTaglibTracker(String prefix, String uri, IStructuredDocumentRegion anchorStructuredDocumentRegion, CMDocument tldCMDocument) { getTaglibTrackers().add(new TaglibTracker(uri, prefix, tldCMDocument, anchorStructuredDocumentRegion)); } /** * Enables a TLD owning the given prefix loaded from the given URI at * the anchorStructuredDocumentRegion. The list of * additionalCMDocuments will claim to not know any of its tags at * positions earlier than that IStructuredDocumentRegion's position. * * For taglib directives, the taglib is the anchor while taglibs * registered through include directives use the parent document's * include directive as their anchor. * * @param prefix * @param uri * @param anchorStructuredDocumentRegion */ protected void enableTaglibFromURI(String prefix, String uri, IStructuredDocumentRegion anchorStructuredDocumentRegion) { enableTags(prefix, uri, anchorStructuredDocumentRegion); if (_debug) { System.out.println("TLDCMDocumentManager registered a tracker for " + uri + " with prefix " + prefix); //$NON-NLS-2$//$NON-NLS-1$ } } private void enableTags(String prefix, String uri, IStructuredDocumentRegion anchorStructuredDocumentRegion) { if (prefix == null || uri == null || bannedPrefixes.contains(prefix)) return; // Try to load the CMDocument for this URI CMDocument tld = getCMDocument(uri); if (tld == null || !(tld instanceof TLDDocument)) { if (_debug) { System.out.println("TLDCMDocumentManager failed to create a CMDocument for " + uri); //$NON-NLS-1$ } return; } registerTaglib(prefix, uri, anchorStructuredDocumentRegion, tld); } /** * Enables a TLD owning the given prefix loaded from the given URI at * the anchorStructuredDocumentRegion. The list of * additionalCMDocuments will claim to not know any of its tags at * positions earlier than that IStructuredDocumentRegion's position. * * For taglib directives, the taglib is the anchor while taglibs * registered through include directives use the parent document's * include directive as their anchor. * * @param prefix * @param uri * @param taglibStructuredDocumentRegion */ protected void enableTagsInDir(String prefix, String tagdir, IStructuredDocumentRegion anchorStructuredDocumentRegion) { enableTags(prefix, tagdir, anchorStructuredDocumentRegion); if (_debug) { System.out.println("TLDCMDocumentManager registered a tracker for directory" + tagdir + " with prefix " + prefix); //$NON-NLS-2$//$NON-NLS-1$ } } protected void processRegionCollection(ITextRegionCollection regionCollection, IStructuredDocumentRegion anchorStructuredDocumentRegion, XMLSourceParser textSource) { /* * Would test > 1, but since we only care if there are 8 (<%@, * taglib, uri, =, where, prefix, =, what) [or 4 for include * directives] */ if (regionCollection.getNumberOfRegions() > 4 && regionCollection.getRegions().get(1).getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) { ITextRegion name = regionCollection.getRegions().get(1); boolean taglibDetected = false; boolean taglibDirectiveDetected = false; boolean includeDetected = false; boolean includeDirectiveDetected = false; int startOffset = regionCollection.getStartOffset(name); int textLength = name.getTextLength(); taglibDetected = textSource.regionMatches(startOffset, textLength, JSP12TLDNames.TAGLIB); if (!taglibDetected) taglibDirectiveDetected = textSource.regionMatches(startOffset, textLength, JSP12Namespace.ElementName.DIRECTIVE_TAGLIB); if (!taglibDirectiveDetected) includeDetected = textSource.regionMatches(startOffset, textLength, JSP12TLDNames.INCLUDE); if (!includeDetected) includeDirectiveDetected = textSource.regionMatches(startOffset, textLength, JSP12Namespace.ElementName.DIRECTIVE_INCLUDE); if (taglibDetected || taglibDirectiveDetected) { processTaglib(regionCollection, anchorStructuredDocumentRegion, textSource); } else if (includeDetected || includeDirectiveDetected) { processInclude(regionCollection, anchorStructuredDocumentRegion, textSource); } } else if (regionCollection.getNumberOfRegions() > 1 && DOMRegionContext.XML_TAG_OPEN.equals(regionCollection.getFirstRegion().getType())) { processXMLStartTag(regionCollection, anchorStructuredDocumentRegion, textSource); } } public void nodeParsed(IStructuredDocumentRegion structuredDocumentRegion) { if (!preludesHandled) { handlePreludes(); preludesHandled = true; } processRegionCollection(structuredDocumentRegion, structuredDocumentRegion, getParser()); } /** * Process an include directive found by the textSource parser and * anchor any taglibs found within at the * anchorStructuredDocumentRegion. Includes use the including file as * the point of reference, not necessarily the "top" file. */ protected void processInclude(ITextRegionCollection includeDirectiveCollection, IStructuredDocumentRegion anchorStructuredDocumentRegion, XMLSourceParser textSource) { ITextRegionList regions = includeDirectiveCollection.getRegions(); String includedFile = null; boolean isFilename = false; try { for (int i = 2; includedFile == null && i < regions.size(); i++) { ITextRegion region = regions.get(i); if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { if (textSource.regionMatches(includeDirectiveCollection.getStartOffset(region), region.getTextLength(), JSP12TLDNames.FILE)) { isFilename = true; } else { isFilename = false; } } else if (isFilename && region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { includedFile = textSource.getText(includeDirectiveCollection.getStartOffset(region), region.getTextLength()); isFilename = false; } } } catch (StringIndexOutOfBoundsException sioobExc) { // nothing to be done includedFile = null; } if (fProcessIncludes && includedFile != null) { // strip any extraneous quotes and white space includedFile = StringUtils.strip(includedFile).trim(); IPath filePath = null; /* * The resolution of the included fragment should use the file * containing the directive as the base reference, not always * the main JSP being invoked. Verified behavior with Apache * Tomcat 5.5.20. */ IPath modelBaseLocation = TaglibController.getLocation(TLDCMDocumentManager.this); if(modelBaseLocation != null) { if (getIncludes().isEmpty()) filePath = FacetModuleCoreSupport.resolve(modelBaseLocation, includedFile); else filePath = FacetModuleCoreSupport.resolve((IPath) getIncludes().peek(), includedFile); } // check for "loops" if (filePath != null && !getIncludes().contains(filePath) && !filePath.equals(modelBaseLocation)) { /* * Prevent slow performance when editing scriptlet part of * the JSP by only processing includes if they've been * modified. The IncludeHelper remembers any CMDocuments * created from the files it parses. Caching the URI and * prefix/tagdir allows us to just enable the CMDocument * when the previously parsed files. * * REMAINING PROBLEM: fTLDCMReferencesMap does not map * from a fragment's path and also include all of the CM * references in fragments that *it* includes. The * fragments that it includes won't have its CM references * loaded, but then we'd need to record the URI and * location of the included fragment to resolve them * correctly, modifying enableTaglib() to also take a base * path and resolve the URI appropriately. */ if (hasAnyIncludeBeenModified(filePath)) { getIncludes().push(filePath); IncludeHelper includeHelper = new IncludeHelper(anchorStructuredDocumentRegion, getParser()); includeHelper.parse(filePath); //bug391852, taglib are nor seen if included indirectly List references = new ArrayList(); List trackers = getTaglibTrackers(); for (Iterator it = trackers.iterator(); it.hasNext();) { TaglibTracker tracker = (TaglibTracker) it.next(); TLDCMDocumentReference reference = new TLDCMDocumentReference(); reference.prefix = tracker.getPrefix(); reference.uri = tracker.getURI(); if (!references.contains(reference)){ references.add(reference); } } references.addAll(includeHelper.taglibReferences); fTLDCMReferencesMap.put(filePath, references); if (getParser() instanceof JSPCapableParser) { for (int i = 0; references != null && i < references.size(); i++) { TLDCMDocumentReference reference = (TLDCMDocumentReference) references.get(i); ((JSPCapableParser) getParser()).addNestablePrefix(new TagMarker(reference.prefix + ":")); //$NON-NLS-1$ } } /* * TODO: walk up the include hierarchy and add * these references to each of the parents? */ getIncludes().pop(); } else { // Add from that saved list of uris/prefixes/documents List references = (List) fTLDCMReferencesMap.get(filePath); for (int i = 0; references != null && i < references.size(); i++) { TLDCMDocumentReference reference = (TLDCMDocumentReference) references.get(i); /* * The uri might not be resolved properly if * relative to the JSP fragment. */ enableTaglibFromURI(reference.prefix, reference.uri, anchorStructuredDocumentRegion); if (getParser() instanceof JSPCapableParser) { ((JSPCapableParser) getParser()).addNestablePrefix(new TagMarker(reference.prefix + ":")); //$NON-NLS-1$ } } } } else if (getIncludes().contains(filePath)) { if (Debug.debugTokenizer) System.out.println("LOOP IN @INCLUDES FOUND: " + filePath); //$NON-NLS-1$ } } } protected void processXMLStartTag(ITextRegionCollection startTagRegionCollection, IStructuredDocumentRegion anchorStructuredDocumentRegion, XMLSourceParser textSource) { ITextRegionList regions = startTagRegionCollection.getRegions(); String uri = null; String prefix = null; boolean isTaglibValue = false; // skip the first two, they're the open bracket and name for (int i = 2; i < regions.size(); i++) { ITextRegion region = regions.get(i); if (region instanceof ITextRegionCollection) { // Handle nested directives processRegionCollection((ITextRegionCollection) region, anchorStructuredDocumentRegion, textSource); } else { // Handle xmlns:xxx=yyy int regionStartOffset = startTagRegionCollection.getStartOffset(region); int regionTextLength = region.getTextLength(); if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { if (regionTextLength > XMLNS_LENGTH && textSource.regionMatches(regionStartOffset, XMLNS_LENGTH, XMLNS)) { prefix = textSource.getText(regionStartOffset + XMLNS_LENGTH, regionTextLength - XMLNS_LENGTH); if (!bannedPrefixes.contains(prefix)) isTaglibValue = true; } else { prefix = null; isTaglibValue = false; } } else if (isTaglibValue && region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { if (prefix != null && prefix.length() > 0) { uri = textSource.getText(regionStartOffset, regionTextLength); uri = StringUtils.strip(uri); int uriLength = uri.length(); if (uri != null && uriLength > 0) { if (uriLength > URN_TLD_LENGTH && uri.startsWith(URN_TLD)) { uri = uri.substring(URN_TLD_LENGTH); } else if (uriLength > URN_TAGDIR_LENGTH && uri.startsWith(URN_TAGDIR)) { uri = uri.substring(URN_TAGDIR_LENGTH); } enableTags(prefix, uri, anchorStructuredDocumentRegion); uri = null; prefix = null; } } } } } } /** * Pulls the URI and prefix from the given taglib directive * IStructuredDocumentRegion and makes sure the tags are known. */ protected void processTaglib(ITextRegionCollection taglibDirectiveCollection, IStructuredDocumentRegion anchorStructuredDocumentRegion, XMLSourceParser textSource) { ITextRegionList regions = taglibDirectiveCollection.getRegions(); String uri = null; String prefix = null; String tagdir = null; String attrName = null; try { for (int i = 2; i < regions.size(); i++) { ITextRegion region = regions.get(i); // remember attribute name int startOffset = taglibDirectiveCollection.getStartOffset(region); int textLength = region.getTextLength(); if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { // String name = textSource.getText(startOffset, // textLength); if (textSource.regionMatches(startOffset, textLength, JSP11TLDNames.PREFIX)) { attrName = JSP11TLDNames.PREFIX; } else if (textSource.regionMatches(startOffset, textLength, JSP12TLDNames.URI)) { attrName = JSP11TLDNames.URI; } else if (textSource.regionMatches(startOffset, textLength, JSP20TLDNames.TAGDIR)) { attrName = JSP20TLDNames.TAGDIR; } else { attrName = null; } } // process value else if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { if (JSP11TLDNames.PREFIX.equals(attrName)) prefix = StringUtils.strip(textSource.getText(startOffset, textLength)); else if (JSP11TLDNames.URI.equals(attrName)) uri = StringUtils.strip(textSource.getText(startOffset, textLength)); else if (JSP20TLDNames.TAGDIR.equals(attrName)) tagdir = StringUtils.strip(textSource.getText(startOffset, textLength)); } } } catch (StringIndexOutOfBoundsException sioobExc) { // nothing to be done uri = null; prefix = null; } if (uri != null && prefix != null && uri.length() > 0 && prefix.length() > 0) { enableTaglibFromURI(prefix, StringUtils.strip(uri), anchorStructuredDocumentRegion); } else if (tagdir != null && prefix != null && tagdir.length() > 0 && prefix.length() > 0) { enableTagsInDir(StringUtils.strip(prefix), StringUtils.strip(tagdir), anchorStructuredDocumentRegion); } } private void registerTaglib(String prefix, String uri, IStructuredDocumentRegion anchorStructuredDocumentRegion, CMDocument tld) { CMNamedNodeMap elements = tld.getElements(); /* * Go through the CMDocument for any tags that must be marked as * block tags starting at the anchoring IStructuredDocumentRegion. * As the document is edited and the IStructuredDocumentRegion * moved around, the block tag enablement will automatically * follow it. */ for (int i = 0; i < elements.getLength(); i++) { TLDElementDeclaration ed = (TLDElementDeclaration) elements.item(i); if (ed.getBodycontent() == JSP12TLDNames.CONTENT_TAGDEPENDENT) addBlockTag(prefix + ":" + ed.getNodeName(), anchorStructuredDocumentRegion); //$NON-NLS-1$ } /* * Since modifications to StructuredDocumentRegions adjacent to a * taglib directive can cause that IStructuredDocumentRegion to be * reported, filter out any duplicated URIs. When the taglib is * actually modified, a full rebuild will occur and no duplicates * will/should be found. */ boolean doTrack = true; List trackers = getTaglibTrackers(); for (int i = 0; i < trackers.size(); i++) { TaglibTracker tracker = (TaglibTracker) trackers.get(i); if (tracker.getPrefix().equals(prefix) && tracker.getURI().equals(uri)) { doTrack = false; } } if (doTrack) { addTaglibTracker(prefix, uri, anchorStructuredDocumentRegion, tld); } } private void resetBlockTags() { if (getParser() == null) return; Iterator names = getParser().getBlockMarkers().iterator(); while (names.hasNext()) { BlockMarker marker = (BlockMarker) names.next(); if (!marker.isGlobal() && marker.getContext() == DOMRegionContext.BLOCK_TEXT) { if (_debug) { System.out.println("TLDCMDocumentManager removing block tag named: " + marker.getTagName()); //$NON-NLS-1$ } names.remove(); } } } public void resetNodes() { if (Debug.debugTaglibs) System.out.println(getClass().getName() + ": resetting"); //$NON-NLS-1$ getIncludes().clear(); resetBlockTags(); resetTaglibTrackers(); } public void setStructuredDocument(IStructuredDocument newDocument) { Assert.isTrue(newDocument != null, "null document"); //$NON-NLS-1$ Assert.isTrue(newDocument.getParser() != null, "null document parser"); //$NON-NLS-1$ Assert.isTrue(newDocument.getParser() instanceof XMLSourceParser, "can only listen to document with a XMLSourceParser"); //$NON-NLS-1$ getSourceParser().removeStructuredDocumentRegionHandler(this); setSourceParser((XMLSourceParser) newDocument.getParser()); getSourceParser().addStructuredDocumentRegionHandler(this); } } protected class IncludeHelper extends DirectiveStructuredDocumentRegionHandler { protected IStructuredDocumentRegion fAnchor = null; protected XMLSourceParser fLocalParser = null; protected XMLSourceParser fParentParser = null; List taglibReferences = null; public IncludeHelper(IStructuredDocumentRegion anchor, XMLSourceParser rootParser) { super(); fAnchor = anchor; fParentParser = rootParser; taglibReferences = new ArrayList(0); } protected void addTaglibTracker(String prefix, String uri, IStructuredDocumentRegion anchorStructuredDocumentRegion, CMDocument tldCMDocument) { super.addTaglibTracker(prefix, uri, anchorStructuredDocumentRegion, tldCMDocument); TLDCMDocumentReference reference = new TLDCMDocumentReference(); reference.prefix = prefix; reference.uri = uri; taglibReferences.add(reference); } protected String getContents(IPath filePath) { return FileContentCache.getInstance().getContents(filePath); } public void nodeParsed(IStructuredDocumentRegion structuredDocumentRegion) { processRegionCollection(structuredDocumentRegion, fAnchor, fLocalParser); } /** * @param path - * the fullpath for the resource to be parsed */ void parse(IPath path) { XMLSourceParser p = (XMLSourceParser) getParser().newInstance(); fLocalParser = p; String s = getContents(path); // Should we consider preludes on this segment? fLocalParser.addStructuredDocumentRegionHandler(IncludeHelper.this); fLocalParser.reset(s); List blockTags = fParentParser.getBlockMarkers(); for (int i = 0; i < blockTags.size(); i++) { BlockMarker marker = (BlockMarker) blockTags.get(i); fLocalParser.addBlockMarker(new BlockMarker(marker.getTagName(), null, marker.getContext(), marker.isCaseSensitive())); } if (fParentParser instanceof JSPCapableParser && fLocalParser instanceof JSPCapableParser) { TagMarker[] knownPrefixes = (TagMarker[]) ((JSPCapableParser) fParentParser).getNestablePrefixes().toArray(new TagMarker[0]); for (int i = 0; i < knownPrefixes.length; i++) { ((JSPCapableParser) fLocalParser).addNestablePrefix(new TagMarker(knownPrefixes[i].getTagName(), null)); } } // force parse fLocalParser.getDocumentRegions(); fLocalParser = null; } public void resetNodes() { } } /** * An entry in the shared cache map */ static class TLDCacheEntry { CMDocument document; long modificationStamp; int referenceCount; } private class TLDCMDocumentReference { String prefix; String uri; public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof TLDCMDocumentReference)) { return false; } TLDCMDocumentReference other = (TLDCMDocumentReference) obj; return equalsString(prefix, other.prefix) && equalsString(uri, other.uri); } private boolean equalsString(String s1, String s2) { return (s1 == null && s2 == null) || s1 != null && s1.equals(s2); } } static final boolean _debug = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/tldcmdocument/manager")); //$NON-NLS-1$ //$NON-NLS-2$ static final boolean _debugCache = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/debug/tldcmdocument/cache")); //$NON-NLS-1$ //$NON-NLS-2$ // will hold the prefixes banned by the specification; taglibs may not use // them protected static List bannedPrefixes = null; private static Hashtable fCache = null; final String XMLNS = "xmlns:"; //$NON-NLS-1$ final String URN_TAGDIR = "urn:jsptagdir:"; final String URN_TLD = "urn:jsptld:"; final int XMLNS_LENGTH = XMLNS.length(); final int URN_TAGDIR_LENGTH = URN_TAGDIR.length(); final int URN_TLD_LENGTH = URN_TLD.length(); static { bannedPrefixes = new ArrayList(7); bannedPrefixes.add("jsp"); //$NON-NLS-1$ bannedPrefixes.add("jspx"); //$NON-NLS-1$ bannedPrefixes.add("java"); //$NON-NLS-1$ bannedPrefixes.add("javax"); //$NON-NLS-1$ bannedPrefixes.add("servlet"); //$NON-NLS-1$ bannedPrefixes.add("sun"); //$NON-NLS-1$ bannedPrefixes.add("sunw"); //$NON-NLS-1$ } /** * Gets all of the known documents. * * @return Returns a Hashtable of either TLDCacheEntrys or WeakReferences * to TLD CMDocuments */ public static Hashtable getSharedDocumentCache() { if (fCache == null) { fCache = new Hashtable(); } return fCache; } public static Object getUniqueIdentifier(ITaglibRecord reference) { if (reference == null) return null; Object identifier = null; switch (reference.getRecordType()) { case (ITaglibRecord.TLD) : { ITLDRecord record = (ITLDRecord) reference; identifier = record.getPath(); } break; case (ITaglibRecord.JAR) : { IJarRecord record = (IJarRecord) reference; identifier = record.getLocation(); } break; case (ITaglibRecord.TAGDIR) : { ITagDirRecord record = (ITagDirRecord) reference; identifier = record.getPath(); } break; case (ITaglibRecord.URL) : { IURLRecord record = (IURLRecord) reference; identifier = record.getURL(); } break; default : identifier = reference; break; } return identifier; } private CMDocumentFactoryTLD fCMDocumentBuilder = null; private DirectiveStructuredDocumentRegionHandler fDirectiveHandler = null; /** * The locally-know list of CMDocuments */ private Hashtable fDocuments = null; // timestamp cache to prevent excessive reparsing // of included files // IPath (filepath) > Long (modification stamp) HashMap fInclude2TimestampMap = new HashMap(); private Stack fIncludes = null; private XMLSourceParser fParser = null; private List fTaglibTrackers = null; Map fTLDCMReferencesMap = new HashMap(); boolean fProcessIncludes = true; boolean preludesHandled = false; public TLDCMDocumentManager() { super(); } public void clearCache() { if (_debugCache) { System.out.println("TLDCMDocumentManager cleared its private CMDocument cache"); //$NON-NLS-1$ } for (Iterator iter = getDocuments().keySet().iterator(); iter.hasNext();) { Object key = iter.next(); synchronized (getSharedDocumentCache()) { Object o = getSharedDocumentCache().get(key); if (o instanceof TLDCacheEntry) { TLDCacheEntry entry = (TLDCacheEntry) o; entry.referenceCount--; if (entry.referenceCount <= 0) { getSharedDocumentCache().put(key, new SoftReference(entry)); } } } } } /** * Derives an unique cache key for the give URI. The URI is "resolved" and * a unique value generated from the result. This ensures that two * different relative references from different files do not have * overlapping TLD records in the shared cache if they don't resolve to * the same TLD. * * @param uri * @return */ protected Object getCacheKey(String uri) { IPath currentParserPath = getCurrentParserPath(); if (currentParserPath == null) return null; ITaglibRecord record = TaglibIndex.resolve(currentParserPath.toString(), uri, false); if (record != null) { return getUniqueIdentifier(record); } String location = URIResolverPlugin.createResolver().resolve(getCurrentBaseLocation().toString(), null, uri); return location; } /** * Return the CMDocument at the uri (cached) */ protected CMDocument getCMDocument(String uri) { if (uri == null || uri.length() == 0) return null; String reference = uri; Object cacheKey = getCacheKey(reference); if (cacheKey == null) return null; CMDocument doc = (CMDocument) getDocuments().get(cacheKey); if (doc == null) { long lastModified = getModificationStamp(reference); /* * If hasn't been moved into the local table, do so and increment * the count. A local URI reference can be different depending on * the file from which it was referenced. Use a computed key to * keep them straight. */ Object o = getSharedDocumentCache().get(cacheKey); if (o != null) { if (o instanceof TLDCacheEntry) { TLDCacheEntry entry = (TLDCacheEntry) o; if (_debugCache) { System.out.println("TLDCMDocument cache hit on " + cacheKey); } if (entry != null && entry.modificationStamp != IResource.NULL_STAMP && entry.modificationStamp >= lastModified) { doc = entry.document; entry.referenceCount++; } else { getSharedDocumentCache().remove(cacheKey); } } else if (o instanceof Reference) { TLDCacheEntry entry = (TLDCacheEntry) ((Reference) o).get(); if (entry != null) { if (entry.modificationStamp != IResource.NULL_STAMP && entry.modificationStamp >= lastModified) { doc = entry.document; entry.referenceCount = 1; getSharedDocumentCache().put(cacheKey, entry); } } else { getSharedDocumentCache().remove(cacheKey); } } } /* No document was found cached, create a new one and share it */ if (doc == null) { if (_debugCache) { System.out.println("TLDCMDocument cache miss on " + cacheKey); } CMDocument document = loadTaglib(reference); if (document != null) { TLDCacheEntry entry = new TLDCacheEntry(); doc = entry.document = document; entry.referenceCount = 1; entry.modificationStamp = lastModified; getSharedDocumentCache().put(cacheKey, entry); } } if (doc != null) { getDocuments().put(cacheKey, doc); } } return doc; } private long getModificationStamp(String reference) { IPath currentParserPath = getCurrentParserPath(); if (currentParserPath == null) { return IResource.NULL_STAMP; } ITaglibRecord record = TaglibIndex.resolve(currentParserPath.toString(), reference, false); long modificationStamp = IResource.NULL_STAMP; if (record != null) { switch (record.getRecordType()) { case (ITaglibRecord.TLD) : { IFile tldfile = ResourcesPlugin.getWorkspace().getRoot().getFile(((ITLDRecord) record).getPath()); if (tldfile.isAccessible()) { modificationStamp = tldfile.getModificationStamp(); } } break; case (ITaglibRecord.JAR) : { File jarfile = new File(((IJarRecord) record).getLocation().toOSString()); if (jarfile.exists()) { try { modificationStamp = jarfile.lastModified(); } catch (SecurityException e) { modificationStamp = IResource.NULL_STAMP; } } } break; case (ITaglibRecord.TAGDIR) : { IFolder tagFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(((ITagDirRecord) record).getPath()); if (tagFolder.isAccessible()) { IResource[] members; try { members = tagFolder.members(); for (int i = 0; i < members.length; i++) { modificationStamp = Math.max(modificationStamp, members[i].getModificationStamp()); } } catch (CoreException e) { modificationStamp = IResource.NULL_STAMP; } } } break; case (ITaglibRecord.URL) : { String loc = ((IURLRecord) record).getBaseLocation(); if (loc != null && loc.endsWith(".jar")) { //$NON-NLS-1$ File jarfile = new File(loc); if (jarfile.exists()) { try { modificationStamp = jarfile.lastModified(); } catch (SecurityException e) { modificationStamp = IResource.NULL_STAMP; } } } } break; default : break; } } return modificationStamp; } /** * Gets the cMDocumentBuilder. * * @return Returns a CMDocumentFactoryTLD, since it has more builder * methods */ protected CMDocumentFactoryTLD getCMDocumentBuilder() { if (fCMDocumentBuilder == null) fCMDocumentBuilder = new CMDocumentFactoryTLD(); return fCMDocumentBuilder; } public List getCMDocumentTrackers(int offset) { List validDocs = new ArrayList(); Object[] alldocs = getTaglibTrackers().toArray(); for (int i = 0; i < alldocs.length; i++) { TaglibTracker aTracker = (TaglibTracker) alldocs[i]; if (aTracker.getStructuredDocumentRegion().getStartOffset() <= offset || offset < 0) { validDocs.add(aTracker); } } return validDocs; } public List getCMDocumentTrackers(String prefix, int offset) { List validDocs = new ArrayList(); Object[] alldocs = getTaglibTrackers().toArray(); for (int i = 0; i < alldocs.length; i++) { TaglibTracker aTracker = (TaglibTracker) alldocs[i]; /** * '<' is used to support the immediate use of a custom tag in jspx files (instead of '<=') */ if ((aTracker.getStructuredDocumentRegion().getStartOffset() <= offset || offset < 0) && aTracker.getPrefix().equals(prefix)) { validDocs.add(aTracker); } } return validDocs; } /** * Return the filesystem location in the current parser. This method is * called while recursing through included fragments, so it much check the * include stack. The filesystem location is needed for common URI * resolution in case the Taglib Index doesn't know the URI being loaded. * * @return */ IPath getCurrentBaseLocation() { IPath baseLocation = null; IPath path = getCurrentParserPath(); if (path == null || path.segmentCount() < 2) return path; baseLocation = ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); if (baseLocation == null) { baseLocation = path; } return baseLocation; } /** * Return the path used in the current parser. This method is called while * recursing through included fragments, so it much check the include * stack. * * @return */ IPath getCurrentParserPath() { IPath path = null; if (!getIncludes().isEmpty()) { path = (IPath) getIncludes().peek(); } else { path = TaglibController.getLocation(this); } return path; } protected DirectiveStructuredDocumentRegionHandler getDirectiveStructuredDocumentRegionHandler() { if (fDirectiveHandler == null) fDirectiveHandler = new DirectiveStructuredDocumentRegionHandler(); return fDirectiveHandler; } /** * Gets the documents. * * @return Returns a java.util.Hashtable */ public Hashtable getDocuments() { if (fDocuments == null) fDocuments = new Hashtable(); return fDocuments; } /** * Gets the includes. * * @return Returns a Stack */ protected Stack getIncludes() { if (fIncludes == null) fIncludes = new Stack(); return fIncludes; } XMLSourceParser getParser() { return fParser; } public XMLSourceParser getSourceParser() { return fParser; } public StructuredDocumentRegionHandler getStructuredDocumentRegionHandler() { return getDirectiveStructuredDocumentRegionHandler(); } /** * * @return java.util.List */ public List getTaglibTrackers() { if (fTaglibTrackers == null) fTaglibTrackers = new ArrayList(); return fTaglibTrackers; } void handlePreludes() { IStructuredDocumentRegion anchor = new ZeroStructuredDocumentRegion(null, -1); fProcessIncludes = false; IPath currentPath = getCurrentParserPath(); if (currentPath != null) { PropertyGroup[] propertyGroups = DeploymentDescriptorPropertyCache.getInstance().getPropertyGroups(currentPath); for(int k = 0; k < propertyGroups.length; k++) { IPath[] preludes = propertyGroups[k].getIncludePrelude(); for (int i = 0; i < preludes.length; i++) { if (!getIncludes().contains(preludes[i]) && !preludes[i].equals(currentPath)) { getIncludes().push(preludes[i]); if (getParser() != null) { IncludeHelper includeHelper = new IncludeHelper(anchor, getParser()); includeHelper.parse(preludes[i]); List references = includeHelper.taglibReferences; fTLDCMReferencesMap.put(preludes[i], references); if (getParser() instanceof JSPCapableParser) { for (int j = 0; j < references.size(); j++) { TLDCMDocumentReference reference = (TLDCMDocumentReference) references.get(j); ((JSPCapableParser) getParser()).addNestablePrefix(new TagMarker(reference.prefix + ":")); //$NON-NLS-1$ } } } else Logger.log(Logger.WARNING, "Warning: parser text was requested by " + getClass().getName() + " but none was available; taglib support disabled"); //$NON-NLS-1$ //$NON-NLS-2$ getIncludes().pop(); } } } } fProcessIncludes = true; } /** * @param filePath * the path to check for modification */ boolean hasAnyIncludeBeenModified(IPath filePath) { boolean result = false; // check the top level if (hasBeenModified(filePath)) { result = true; } else { // check all includees Iterator iter = fInclude2TimestampMap.keySet().iterator(); while (iter.hasNext()) { if (hasBeenModified((IPath) iter.next())) { result = true; break; } } } return result; } /** * @param filename * @return */ boolean hasBeenModified(IPath filePath) { boolean result = false; // quick filename/timestamp cache check here... IFile f = null; if (f == null && filePath.segmentCount() > 1) { f = ResourcesPlugin.getWorkspace().getRoot().getFile(filePath); } if (f != null && f.exists()) { Long currentStamp = new Long(f.getModificationStamp()); Object o = fInclude2TimestampMap.get(filePath); if (o != null) { Long previousStamp = (Long) o; // stamps don't match, file changed if (currentStamp.longValue() != previousStamp.longValue()) { result = true; // store for next time fInclude2TimestampMap.put(filePath, currentStamp); } } else { // return true, since we've not encountered this file yet. result = true; // store for next time fInclude2TimestampMap.put(filePath, currentStamp); } } return result; } public void indexChanged(ITaglibIndexDelta event) { synchronized (getSharedDocumentCache()) { Iterator values = getSharedDocumentCache().values().iterator(); while (values.hasNext()) { Object o = values.next(); if (o instanceof Reference) { values.remove(); } } } } /** * Loads the taglib from the specified URI. It must point to a valid * taglib descriptor to work. */ protected CMDocument loadTaglib(String uri) { CMDocument document = null; IPath currentPath = getCurrentParserPath(); if (currentPath != null) { ITaglibRecord record = TaglibIndex.resolve(currentPath.toString(), uri, false); if (record != null) { document = getCMDocumentBuilder().createCMDocument(record); } else { /* Not a very-often used code path (we hope) */ IPath currentBaseLocation = getCurrentBaseLocation(); if (currentBaseLocation != null) { String location = URIResolverPlugin.createResolver().resolve(currentBaseLocation.toString(), null, uri); if (location != null) { if (_debug) { System.out.println("Loading tags from " + uri + " at " + location); //$NON-NLS-2$//$NON-NLS-1$ } document = getCMDocumentBuilder().createCMDocument(location); } } } } return document; } protected void resetTaglibTrackers() { if (_debug) { System.out.println("TLDCMDocumentManager cleared its taglib trackers\n"); //$NON-NLS-1$ } preludesHandled = false; getTaglibTrackers().clear(); } public void setSourceParser(XMLSourceParser parser) { if (fParser != null) fParser.removeStructuredDocumentRegionHandler(getStructuredDocumentRegionHandler()); fParser = parser; if (fParser != null) fParser.addStructuredDocumentRegionHandler(getStructuredDocumentRegionHandler()); } }