blob: d9b7695acdd65013597e5cffd15758a74feb981a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 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.text;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP12TLDNames;
import org.eclipse.jst.jsp.core.internal.encoding.JSPDocumentHeadContentDetector;
import org.eclipse.jst.jsp.core.internal.parser.JSPSourceParser;
import org.eclipse.jst.jsp.core.internal.provisional.JSP11Namespace;
import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace;
import org.eclipse.jst.jsp.core.internal.provisional.text.IJSPPartitionTypes;
import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
import org.eclipse.wst.html.core.internal.text.StructuredTextPartitionerForHTML;
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.StructuredDocumentRegionParser;
import org.eclipse.wst.sse.core.internal.parser.ForeignRegion;
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.IStructuredTextPartitioner;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.text.rules.StructuredTextPartitioner;
import org.eclipse.wst.sse.core.internal.util.StringUtils;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.core.internal.text.rules.StructuredTextPartitionerForXML;
public class StructuredTextPartitionerForJSP extends StructuredTextPartitioner {
private class PrefixListener implements StructuredDocumentRegionHandler, StructuredDocumentRegionHandlerExtension {
// track the list of prefixes introduced by taglib directives
private List fCustomActionPrefixes = null;
private String fLastTrue = null;
public PrefixListener() {
super();
fCustomActionPrefixes = new ArrayList(1);
resetNodes();
}
private JSPSourceParser getTextSource() {
return (JSPSourceParser) structuredDocument.getParser();
}
public void nodeParsed(IStructuredDocumentRegion sdRegion) {
// Largely taken from the TLDCMDocumentManager
// could test > 1, but since we only care if there are 8 (<%@,
// taglib, uri, =, where, prefix, =, what) [or 4 for includes]
if (sdRegion.getNumberOfRegions() > 4 && sdRegion.getRegions().get(1).getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) {
ITextRegion nameRegion = sdRegion.getRegions().get(1);
try {
boolean tablibdetected = false;
boolean directiveTaglibdetected;
int startOffset = sdRegion.getStartOffset(nameRegion);
int textLength = nameRegion.getTextLength();
if (getTextSource() != null) {
tablibdetected = getTextSource().regionMatches(startOffset, textLength, JSP12TLDNames.TAGLIB);
directiveTaglibdetected = getTextSource().regionMatches(startOffset, textLength, JSP12Namespace.ElementName.DIRECTIVE_TAGLIB);
}
else {
// old fashioned way
String directiveName = getTextSource().getText(startOffset, textLength);
tablibdetected = directiveName.equals(JSP12TLDNames.TAGLIB);
directiveTaglibdetected = directiveName.equals(JSP12Namespace.ElementName.DIRECTIVE_TAGLIB);
}
if (tablibdetected || directiveTaglibdetected) {
processTaglib(sdRegion);
}
}
catch (StringIndexOutOfBoundsException sioobExc) {
// ISSUE: why is this "normal" here?
//do nothing
}
}
}
private void processTaglib(IStructuredDocumentRegion taglibStructuredDocumentRegion) {
ITextRegionList regions = taglibStructuredDocumentRegion.getRegions();
String prefixValue = null;
boolean prefixnameDetected = false;
try {
for (int i = 0; i < regions.size(); i++) {
ITextRegion region = regions.get(i);
int startOffset = taglibStructuredDocumentRegion.getStartOffset(region);
int textLength = region.getTextLength();
if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
prefixnameDetected = getTextSource().regionMatches(startOffset, textLength, JSP12TLDNames.PREFIX);
//String regionText =
// fTextSource.getText(startOffset, textLength);
//prefixname =
// regionText.equals(JSP12TLDNames.PREFIX);
}
else if (prefixnameDetected && region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
prefixValue = getTextSource().getText(startOffset, textLength);
}
}
}
catch (StringIndexOutOfBoundsException sioobExc) {
// nothing to be done
prefixValue = null;
}
if (prefixValue != null) {
String prefixText = StringUtils.strip(prefixValue) + ":"; //$NON-NLS-1$
if (!fCustomActionPrefixes.contains(prefixText)) {
if(debugPrefixListener == true) {
System.out.println("StructuredTextPartitionerForJSP.PrefixListener learning prefix: " + prefixText); //$NON-NLS-1$
}
fCustomActionPrefixes.add(prefixText);
}
}
}
public void resetNodes() {
fLastTrue = null;
fCustomActionPrefixes.clear();
fCustomActionPrefixes.add(JSP11Namespace.JSP_TAG_PREFIX + ":"); //$NON-NLS-1$
if(debugPrefixListener == true) {
System.out.println("StructuredTextPartitionerForJSP.PrefixListener forgetting learned prefixes"); //$NON-NLS-1$
}
}
public void setStructuredDocument(IStructuredDocument newDocument) {
resetNodes();
((StructuredDocumentRegionParser) structuredDocument.getParser()).removeStructuredDocumentRegionHandler(this);
if(newDocument != null) {
((StructuredDocumentRegionParser) newDocument.getParser()).addStructuredDocumentRegionHandler(this);
}
}
public boolean startsWithCustomActionPrefix(String tagname) {
if (tagname.equals(fLastTrue))
return true;
for (int i = 0; i < fCustomActionPrefixes.size(); i++)
if (tagname.startsWith((String) fCustomActionPrefixes.get(i))) {
fLastTrue = tagname;
return true;
}
return false;
}
}
static final boolean debugPrefixListener = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jst.jsp.core/partitioner/prefixlistener")); //$NON-NLS-1$ //$NON-NLS-2$
// for compatibility with v5.1.0, we'll reuse ST_JSP_DIRECTIVE for action
// tags
private final static boolean fEnableJSPActionPartitions = true;
// list of valid JSP 1.2 tag and action names
private static List fJSPActionTagNames = null;
private static final String HTML_MIME_TYPE = "text/html"; //$NON-NLS-1$
private static final String XHTML_MIME_TYPE = "text/xhtml"; //$NON-NLS-1$
private static final String XML_MIME_TYPE = "text/xml"; //$NON-NLS-1$
private final static String[] fConfiguredContentTypes = new String[]{IJSPPartitionTypes.JSP_DEFAULT, IJSPPartitionTypes.JSP_DEFAULT_EL, IJSPPartitionTypes.JSP_DIRECTIVE, IJSPPartitionTypes.JSP_CONTENT_DELIMITER, IJSPPartitionTypes.JSP_CONTENT_JAVA, IJSPPartitionTypes.JSP_CONTENT_JAVASCRIPT, IJSPPartitionTypes.JSP_COMMENT};
/**
* @return
*/
public static String[] getConfiguredContentTypes() {
return fConfiguredContentTypes;
}
private IStructuredTextPartitioner fEmbeddedPartitioner = null;
/**
* Assume language=java by default ... client, such as
* PageDirectiveAdapter, must set language of document partitioner,
* if/when it changes.
*/
private String fLanguage = "java"; //$NON-NLS-1$
private PrefixListener fPrefixParseListener;
/**
* Constructor for JSPDocumentPartioner.
*/
public StructuredTextPartitionerForJSP() {
super();
if (fJSPActionTagNames == null) {
fJSPActionTagNames = new ArrayList(); // uses .equals() for
// contains()
fJSPActionTagNames.add(JSP12Namespace.ElementName.DECLARATION);
// fJSPActionTagNames.add(JSP12Namespace.ElementName.DIRECTIVE_INCLUDE);
// fJSPActionTagNames.add(JSP12Namespace.ElementName.DIRECTIVE_PAGE);
// fJSPActionTagNames.add(JSP12Namespace.ElementName.DIRECTIVE_TAGLIB);
fJSPActionTagNames.add(JSP12Namespace.ElementName.EXPRESSION);
fJSPActionTagNames.add(JSP12Namespace.ElementName.FALLBACK);
fJSPActionTagNames.add(JSP12Namespace.ElementName.FORWARD);
fJSPActionTagNames.add(JSP12Namespace.ElementName.GETPROPERTY);
fJSPActionTagNames.add(JSP12Namespace.ElementName.INCLUDE);
fJSPActionTagNames.add(JSP12Namespace.ElementName.PARAM);
fJSPActionTagNames.add(JSP12Namespace.ElementName.PARAMS);
fJSPActionTagNames.add(JSP12Namespace.ElementName.PLUGIN);
// fJSPActionTagNames.add(JSP12Namespace.ElementName.ROOT);
fJSPActionTagNames.add(JSP12Namespace.ElementName.SCRIPTLET);
fJSPActionTagNames.add(JSP12Namespace.ElementName.SETPROPERTY);
fJSPActionTagNames.add(JSP12Namespace.ElementName.TEXT);
fJSPActionTagNames.add(JSP12Namespace.ElementName.USEBEAN);
}
}
/**
* @see org.eclipse.jface.text.IDocumentPartitioner#connect(org.eclipse.jface.text.IDocument)
*/
public void connect(IDocument document) {
super.connect(document);
fSupportedTypes = null;
// be extra paranoid
if (fEnableJSPActionPartitions && structuredDocument.getParser() instanceof JSPSourceParser) {
StructuredDocumentRegionParser parser = (StructuredDocumentRegionParser) structuredDocument.getParser();
parser.removeStructuredDocumentRegionHandler(fPrefixParseListener);
fPrefixParseListener = new PrefixListener();
parser.addStructuredDocumentRegionHandler(fPrefixParseListener);
}
}
private IStructuredTextPartitioner createStructuredTextPartitioner(IStructuredDocument structuredDocument) {
IStructuredTextPartitioner result = new NullStructuredDocumentPartitioner();
JSPDocumentHeadContentDetector jspHeadContentDetector = new JSPDocumentHeadContentDetector();
jspHeadContentDetector.set(structuredDocument);
String contentType;
try {
contentType = jspHeadContentDetector.getContentType();
}
catch (IOException e) {
// should be impossible in this context
throw new Error(e);
}
if (contentType == null) {
contentType = "text/html"; //$NON-NLS-1$
}
// we currently only have two ... eventually should
// make or tie-in to existing registry.
if (contentType.equalsIgnoreCase(HTML_MIME_TYPE)) {
result = new StructuredTextPartitionerForHTML();
result.connect(structuredDocument);
}
else if (contentType.equalsIgnoreCase(XHTML_MIME_TYPE)) {
result = new StructuredTextPartitionerForHTML();
result.connect(structuredDocument);
}
else if (contentType.equalsIgnoreCase(XML_MIME_TYPE)) {
result = new StructuredTextPartitionerForXML();
result.connect(structuredDocument);
}
return result;
}
/**
* @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
*/
public void disconnect() {
// we'll check for null document, just for bullet proofing (incase
// disconnnect is called without corresponding connect.
if (structuredDocument != null) {
StructuredDocumentRegionParser parser = (StructuredDocumentRegionParser) structuredDocument.getParser();
if (fPrefixParseListener != null)
parser.removeStructuredDocumentRegionHandler(fPrefixParseListener);
fPrefixParseListener = null;
}
if (fEmbeddedPartitioner != null) {
fEmbeddedPartitioner.disconnect();
// https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4909
/**
* force recreation when reconnected
*/
fEmbeddedPartitioner = null;
}
// super.disconnect should come at end, since it (may) set
// structuredDocument to null
super.disconnect();
}
public String getDefaultPartitionType() {
return getEmbeddedPartitioner().getDefaultPartitionType();
}
/**
* Returns the embeddedPartitioner.
*
* @return IStructuredTextPartitioner
*/
public IStructuredTextPartitioner getEmbeddedPartitioner() {
if (fEmbeddedPartitioner == null) {
fEmbeddedPartitioner = createStructuredTextPartitioner(structuredDocument);
fEmbeddedPartitioner.connect(structuredDocument);
}
return fEmbeddedPartitioner;
}
/**
* Returns the language.
*
* @return String
*/
public String getLanguage() {
return fLanguage;
}
private List getLocalLegalContentTypes() {
List types = new ArrayList();
Object[] configuredTypes = getConfiguredContentTypes();
for (int i = 0; i < configuredTypes.length; i++)
types.add(configuredTypes[i]);
return types;
}
private String getParentName(IStructuredDocumentRegion sdRegion) {
String result = "UNKNOWN"; //$NON-NLS-1$
while (sdRegion != null && isValidJspActionRegionType(sdRegion.getType()))
sdRegion = sdRegion.getPrevious();
if (sdRegion != null) {
ITextRegionList regions = sdRegion.getRegions();
// only find parent names from a start tag
if (regions.size() > 1) {
ITextRegion r = regions.get(1);
if (regions.get(0).getType().equals(DOMRegionContext.XML_TAG_OPEN) && r.getType().equals(DOMRegionContext.XML_TAG_NAME)) {
result = sdRegion.getText(r);
}
}
}
return result;
}
protected String getPartitionType(ForeignRegion region, int offset) {
return getEmbeddedPartitioner().getPartitionType(region, offset);
}
public String getPartitionType(ITextRegion region, int offset) {
String result = null;
final String region_type = region.getType();
if (region_type == DOMJSPRegionContexts.JSP_CONTENT) {
result = getPartitionTypeForDocumentLanguage();
}
else if (region_type == DOMJSPRegionContexts.JSP_COMMENT_TEXT || region_type == DOMJSPRegionContexts.JSP_COMMENT_OPEN || region_type == DOMJSPRegionContexts.JSP_COMMENT_CLOSE)
result = IJSPPartitionTypes.JSP_COMMENT;
else if (region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME || region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || region_type == DOMJSPRegionContexts.JSP_DIRECTIVE_CLOSE)
result = IJSPPartitionTypes.JSP_DIRECTIVE;
else if (region_type == DOMJSPRegionContexts.JSP_CLOSE || region_type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN || region_type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN || region_type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN)
result = IJSPPartitionTypes.JSP_CONTENT_DELIMITER;
else if (region_type == DOMJSPRegionContexts.JSP_ROOT_TAG_NAME)
result = IJSPPartitionTypes.JSP_DEFAULT;
else if (region_type == DOMJSPRegionContexts.JSP_EL_OPEN || region_type == DOMJSPRegionContexts.JSP_EL_CONTENT || region_type == DOMJSPRegionContexts.JSP_EL_CLOSE || region_type == DOMJSPRegionContexts.JSP_EL_DQUOTE
|| region_type == DOMJSPRegionContexts.JSP_EL_SQUOTE || region_type == DOMJSPRegionContexts.JSP_EL_QUOTED_CONTENT)
result = IJSPPartitionTypes.JSP_DEFAULT_EL;
else if (region_type == DOMRegionContext.XML_CONTENT) {
// possibly between <jsp:scriptlet>, <jsp:expression>,
// <jsp:declaration>
IStructuredDocumentRegion sdRegion = this.structuredDocument.getRegionAtCharacterOffset(offset);
if (isJspJavaActionName(getParentName(sdRegion)))
result = getPartitionTypeForDocumentLanguage();
else
result = getDefaultPartitionType();
}
else {
result = getEmbeddedPartitioner().getPartitionType(region, offset);
}
return result;
}
public String getPartitionTypeBetween(IStructuredDocumentRegion previousNode, IStructuredDocumentRegion nextNode) {
return getEmbeddedPartitioner().getPartitionTypeBetween(previousNode, nextNode);
}
private String getPartitionTypeForDocumentLanguage() {
String result;
if (fLanguage == null || fLanguage.equalsIgnoreCase("java")) { //$NON-NLS-1$
result = IJSPPartitionTypes.JSP_CONTENT_JAVA;
}
else if (fLanguage.equalsIgnoreCase("javascript")) { //$NON-NLS-1$
result = IJSPPartitionTypes.JSP_CONTENT_JAVASCRIPT;
}
else {
result = IJSPPartitionTypes.JSP_SCRIPT_PREFIX + getLanguage().toUpperCase(Locale.ENGLISH);
}
return result;
}
protected void initLegalContentTypes() {
List combinedTypes = getLocalLegalContentTypes();
if (getEmbeddedPartitioner() != null) {
String[] moreTypes = getEmbeddedPartitioner().getLegalContentTypes();
for (int i = 0; i < moreTypes.length; i++)
combinedTypes.add(moreTypes[i]);
}
fSupportedTypes = new String[0];
combinedTypes.toArray(fSupportedTypes);
}
/**
* @param sdRegion
* @param offset
* @return
*/
private boolean isAction(IStructuredDocumentRegion sdRegion, int offset) {
if (!sdRegion.getType().equals(DOMRegionContext.XML_TAG_NAME))
return false;
// shouldn't get a tag name region type unless a tag name region
// exists
// at [1]
ITextRegion tagNameRegion = sdRegion.getRegions().get(1);
String tagName = sdRegion.getText(tagNameRegion);
// TODO: support custom JSP actions
// the jsp: prefix is already loaded in the prefix listener
// if (fJSPActionTagNames.contains(tagName))
// return true;
return fPrefixParseListener.startsWithCustomActionPrefix(tagName);
}
protected boolean isDocumentRegionBasedPartition(IStructuredDocumentRegion sdRegion, ITextRegion containedChildRegion, int offset) {
String documentRegionContext = sdRegion.getType();
if (containedChildRegion != null) {
if (documentRegionContext.equals(DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) || documentRegionContext.equals(DOMJSPRegionContexts.JSP_ROOT_TAG_NAME)) {
setInternalPartition(offset, containedChildRegion.getLength(), IJSPPartitionTypes.JSP_DIRECTIVE);
return true;
}
if (fEnableJSPActionPartitions && isAction(sdRegion, offset)) {
setInternalPartition(offset, containedChildRegion.getLength(), IJSPPartitionTypes.JSP_DIRECTIVE);
return true;
}
}
return super.isDocumentRegionBasedPartition(sdRegion, containedChildRegion, offset);
}
/**
* @param possibleJspJavaAction
* @return
*/
private boolean isJspJavaActionName(String possibleJspJavaAction) {
return possibleJspJavaAction.equals(JSP11Namespace.ElementName.SCRIPTLET) || possibleJspJavaAction.equals(JSP11Namespace.ElementName.EXPRESSION) || possibleJspJavaAction.equals(JSP11Namespace.ElementName.DECLARATION);
}
private boolean isValidJspActionRegionType(String type) {
// true for anything that can be within <jsp:scriptlet>,
// <jsp:expression>, <jsp:declaration>
return type == DOMRegionContext.XML_CONTENT || type == DOMRegionContext.BLOCK_TEXT || type == DOMRegionContext.XML_CDATA_OPEN || type == DOMRegionContext.XML_CDATA_TEXT || type == DOMRegionContext.XML_CDATA_CLOSE;
}
public IDocumentPartitioner newInstance() {
StructuredTextPartitionerForJSP instance = new StructuredTextPartitionerForJSP();
instance.setEmbeddedPartitioner(createStructuredTextPartitioner(structuredDocument));
instance.setLanguage(fLanguage);
return instance;
}
/**
* Sets the embeddedPartitioner.
*
* @param embeddedPartitioner
* The embeddedPartitioner to set
*/
public void setEmbeddedPartitioner(IStructuredTextPartitioner embeddedPartitioner) {
// https://w3.opensource.ibm.com/bugzilla/show_bug.cgi?id=4909
/**
* manage connected state of embedded partitioner
*/
if(fEmbeddedPartitioner != null && structuredDocument != null) {
fEmbeddedPartitioner.disconnect();
}
this.fEmbeddedPartitioner = embeddedPartitioner;
if(fEmbeddedPartitioner != null && structuredDocument != null) {
fEmbeddedPartitioner.connect(structuredDocument);
}
}
protected void setInternalPartition(int offset, int length, String type) {
//TODO: need to carry this single instance idea further to be
// complete,
// but hopefully this will be less garbage than before (especially for
// HTML, XML,
// naturally!)
internalReusedTempInstance = getEmbeddedPartitioner().createPartition(offset, length, type);
}
/**
* Sets the language.
*
* @param language
* The language to set
*/
public void setLanguage(String language) {
this.fLanguage = language;
}
}