Package org.eclipse.jst.jsp.core.internal.contentmodel.tld

Source Code of org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager$TLDCacheEntry

/*******************************************************************************
* Copyright (c) 2004, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.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);
            List references = 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;
  }

  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;
   
    long lastModified = getModificationStamp(reference);
    CMDocument doc = (CMDocument) getDocuments().get(cacheKey);
    if (doc == null) {
      /*
       * 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 = getModificationStamp(reference);
          getSharedDocumentCache().put(cacheKey, entry);
        }
      }
      if (doc != null) {
        getDocuments().put(cacheKey, doc);
      }
    }
    return doc;
  }

  private long getModificationStamp(String reference) {
    ITaglibRecord record = TaglibIndex.resolve(getCurrentParserPath().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());
  }
}
TOP

Related Classes of org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager$TLDCacheEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
reate', 'UA-20639858-1', 'auto'); ga('send', 'pageview');