blob: f463600b7f6dceb2cde8add7f0ecbb31705acffe [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.parser;
import java.util.Iterator;
import org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.JSP11TLDNames;
import org.eclipse.jst.jsp.core.internal.provisional.JSP12Namespace;
import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
import org.eclipse.wst.sse.core.internal.ltk.parser.StructuredDocumentRegionParser;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.text.CoreNodeList;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.xml.core.internal.parser.XMLStructuredDocumentReParser;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
public class JSPReParser extends XMLStructuredDocumentReParser {
/**
* Allow a reparser to check for extra syntactic cases that require
* parsing beyond the flatNode boundary.
*
* This implementation adds JSP language markers (comments are handled
* elsewhere).
*/
protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionSyntax() {
StructuredDocumentEvent result = super.checkForCrossStructuredDocumentRegionSyntax();
// None of the superclass' cases were valid, so check for JSP cases
if (result == null) {
result = checkForJSP();
}
return result;
}
/**
* A change to a JSP tag can result in all being reparsed.
*/
private StructuredDocumentEvent checkForJSP() {
StructuredDocumentEvent result = null;
result = checkForCriticalKey("<%"); //$NON-NLS-1$
if (result == null)
result = checkForCriticalKey("<%="); //$NON-NLS-1$
if (result == null)
result = checkForCriticalKey("<%!"); //$NON-NLS-1$
if (result == null)
result = checkForCriticalKey("%>"); //$NON-NLS-1$
return result;
}
/**
* If a comment start or end tag is being added or deleted, we'll rescan
* the whole document. The reason is that content that is revealed or
* commented out can effect the interpretation of the rest of the
* document. Note: for now this is very XML/JSP specific, can
* refactor/improve later.
*/
protected StructuredDocumentEvent checkForComments() {
StructuredDocumentEvent result = super.checkForComments();
if (result == null)
result = checkForCriticalKey("<%--"); //$NON-NLS-1$
if (result == null)
result = checkForCriticalKey("--%>"); //$NON-NLS-1$
// we'll also check for these degenerate cases
if (result == null)
result = checkForCriticalKey("<%---%>"); //$NON-NLS-1$
return result;
}
/**
* The core reparsing method ... after the dirty start and dirty end have
* been calculated elsewhere. - this method overrides, does not extend
* super's method. changes/fixes to super may have to be made here as
* well.
*/
protected StructuredDocumentEvent reparse(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) {
StructuredDocumentEvent result = null;
int rescanStart = -1;
int rescanEnd = -1;
boolean firstTime = false;
boolean detectedBreakingChange = false;
//
// "save" the oldNodes (that may be replaced) in a list
CoreNodeList oldNodes = formOldNodes(dirtyStart, dirtyEnd);
if (containsBreakingChange(oldNodes) || isBreakingWithNestedTag(dirtyStart, dirtyEnd)) {
if (Debug.debugTaglibs)
System.out.println("reparse: is taglib or include"); //$NON-NLS-1$
detectedBreakingChange = true;
rescanStart = 0;
rescanEnd = fStructuredDocument.getLength() + fLengthDifference;
oldNodes = formOldNodes(fStructuredDocument.getFirstStructuredDocumentRegion(), fStructuredDocument.getLastStructuredDocumentRegion());
clearTaglibInfo();
}
else if (dirtyStart == null || dirtyEnd == null) {
// dirtyStart or dirty end are null, then that means we didn't
// have a
// cached node, which means we have an empty document, so we
// just need to rescan the changes
rescanStart = 0;
rescanEnd = fChanges.length();
firstTime = true;
}
else {
// set the start of the text to rescan
rescanStart = dirtyStart.getStart();
//
// set the end of the text to rescan
// notice we use the same rationale as for the rescanStart,
// with the added caveat that length has to be added to it,
// to compensate for the new text which has been added or deleted.
// If changes has zero length, then "length" will be negative,
// since
// we are deleting text. Otherwise, use the difference between
// what's selected to be replaced and the length of the new text.
rescanEnd = dirtyEnd.getEnd() + fLengthDifference;
}
// now that we have the old stuff "saved" away, update the document
// with the changes.
fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges);
// ------------------ now the real work
result = core_reparse(rescanStart, rescanEnd, oldNodes, firstTime);
//
// if we did not detect a breaking type of change at the beginning,
// but
// do now, then reparse all! If we did detect them, then we may or may
// not detect again, but presumably we've already set up to re-parsed
// everthing, so no need to do again.
if ((!detectedBreakingChange) && (containsBreakingChange(oldNodes))) {
clearTaglibInfo();
// reparse all
oldNodes = formOldNodes(fStructuredDocument.getFirstStructuredDocumentRegion(), fStructuredDocument.getLastStructuredDocumentRegion());
result = core_reparse(0, fStructuredDocument.getLength(), oldNodes, firstTime);
}
// event is returned to the caller, incase there is
// some optimization they can do
return result;
}
/**
* Verifies that the regions given, representing the contents of a
* IStructuredDocumentRegion, contain regions that could alter the
* behavior of the parser or the parsing of areas outside of the regions
* given.
*/
private boolean isBreakingChange(IStructuredDocumentRegion node, ITextRegionList regions) {
return isTaglibOrInclude(node, regions) || isJspRoot(regions);
}
/**
* Verifies that the regions given, representing the regions touched by a
* text change have: 1) ...an insertion at the textEndOffset of an
* XML_TAG_OPEN that's in it's own IStructuredDocumentRegion and preceded
* by an unended IStructuredDocumentRegion 2) ...a deletion happening in
* an XML_EMPTY_TAG_CLOSE that ends a ITextRegionContainer 3) ...an
* insertion happening with a ' <' character somewhere in an XML attribute
* name or value 4) ...a deletion of a normal XML_TAG_CLOSE since
* subsequent tags become attribute values
*/
private boolean isBreakingWithNestedTag(boolean changesIncludeA_lt, boolean delsIncludeA_gt, IStructuredDocumentRegion parent, ITextRegion region) {
boolean result = false;
IStructuredDocumentRegion previous = parent.getPrevious();
// case 1 test
if (parent.getRegions().size() == 1 && region.getType() == DOMRegionContext.XML_TAG_OPEN && (previous == null || (!previous.isEnded() || previous.getType() == DOMRegionContext.XML_CONTENT))) {
result = true;
}
// case 2 test
if (region instanceof ITextRegionContainer) {
ITextRegionContainer container = (ITextRegionContainer) region;
ITextRegion internal = container.getRegions().get(container.getRegions().size() - 1);
if (internal.getType() == DOMRegionContext.WHITE_SPACE && container.getRegions().size() >= 2)
internal = container.getRegions().get(container.getRegions().size() - 2);
if (internal.getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE) {
result = true;
}
}
// case 3 test
if (changesIncludeA_lt && (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME || region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) {
result = true;
}
// case 4 test
if (delsIncludeA_gt && region.getType() == DOMRegionContext.XML_TAG_CLOSE) {
result = true;
}
return result;
}
/**
* Verifies that the regions given, representing the contents of a
* IStructuredDocumentRegion, includes a jsp:root tag
*/
private boolean isJspRoot(ITextRegionList regions) {
return regions.size() > 1 && regions.get(0).getType() == DOMRegionContext.XML_TAG_OPEN && regions.get(1).getType() == DOMJSPRegionContexts.JSP_ROOT_TAG_NAME;
}
/**
* Verifies that the regions given, representing the contents of a
* IStructuredDocumentRegion, includes a valid taglib directive or include
* directive
*/
private boolean isTaglibOrInclude(IStructuredDocumentRegion node, ITextRegionList regions) {
boolean sizeAndTypesMatch = (regions.size() > 1) && (regions.get(1).getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_NAME) && (regions.get(0).getType() == DOMJSPRegionContexts.JSP_DIRECTIVE_OPEN || regions.get(0).getType() == DOMRegionContext.XML_TAG_OPEN);
if (!sizeAndTypesMatch)
return false;
ITextRegion region = regions.get(1);
String directiveName = node.getText(region);
return sizeAndTypesMatch && (directiveName.equals(JSP11TLDNames.TAGLIB) || directiveName.equals(JSP11TLDNames.INCLUDE) || directiveName.equals(JSP12Namespace.ElementName.DIRECTIVE_TAGLIB) || directiveName.equals(JSP12Namespace.ElementName.DIRECTIVE_INCLUDE));
}
private void clearTaglibInfo() {
if (Debug.debugTaglibs)
System.out.println("clearing taglib info"); //$NON-NLS-1$
RegionParser parser = fStructuredDocument.getParser();
if (parser instanceof StructuredDocumentRegionParser)
((StructuredDocumentRegionParser) parser).resetHandlers();
}
private boolean containsBreakingChange(IStructuredDocumentRegionList list) {
boolean contains = false;
for (int i = 0; i < list.getLength(); i++) {
IStructuredDocumentRegion node = list.item(i);
if (isBreakingChange(node, node.getRegions())) {
contains = true;
break;
}
}
return contains;
}
protected IStructuredDocumentRegion findDirtyEnd(int end) {
IStructuredDocumentRegion result = super.findDirtyEnd(end);
// if not well formed, get one past, if its not null
// now, if any of to-be-scanned flatnodes are the start of a jsp
// region, we'll
// reparse all the way to the end, to be sure we detect embedded
// regions (or not-embedded regions) correctly.
// notice we don't need to do if we're only processing one node.
// notice too we have a strong assumption here that dirtyStart has
// already been found!
//
// note that dirtyEnd is not checked in the do-block below, so we'll
// check it first.
if (isJSPEmbeddedStartOrEnd(result)) {
result = fStructuredDocument.getLastStructuredDocumentRegion();
}
else {
// when end node and start node are the same, we only need the
// above
// check, otherwise, there's a few cases that we'll search the
// rest of the
// flatnodes needlessly.
if (result != dirtyStart) {
IStructuredDocumentRegion searchNode = dirtyStart;
do {
if (isJSPEmbeddedStartOrEnd(searchNode)) {
result = fStructuredDocument.getLastStructuredDocumentRegion();
break;
}
else {
searchNode = searchNode.getNext();
}
// if we get to the current dirty end, or end of
// flatnodes, without finding JSP region then we
// don't need to check further
}
while ((searchNode != result) && (searchNode != null));
}
}
// result should never be null, but cachedNode needs to be protected
// from being changed to null
if (result != null)
fStructuredDocument.setCachedDocumentRegion(result);
dirtyEnd = result;
return dirtyEnd;
}
private boolean isBreakingWithNestedTag(IStructuredDocumentRegion start, IStructuredDocumentRegion end) {
boolean result = false;
boolean changesIncludeA_lt = fChanges != null && fChanges.indexOf('<') >= 0;
boolean delsIncludeA_gt = fDeletedText != null && fDeletedText.indexOf('>') >= 0;
// List regions = new ArrayList();
IStructuredDocumentRegion node = start;
int endReplace = fStart + fLengthToReplace;
while (end != null && node != end.getNext()) {
Iterator i = node.getRegions().iterator();
while (i.hasNext()) {
ITextRegion region = (ITextRegion) i.next();
if (intersects(node, region, fStart, endReplace)) {
result = isBreakingWithNestedTag(changesIncludeA_lt, delsIncludeA_gt, node, region);
if (result)
break;
}
}
node = node.getNext();
if (result)
break;
}
return result;
}
private boolean intersects(IStructuredDocumentRegion node, ITextRegion region, int low, int high) {
int start = node.getStartOffset(region);
int end = node.getEndOffset(region);
return (end >= low && start <= high) || (start <= low && end >= low) || (start <= high && end >= high);
}
/**
* Returns true if potentially could be a jsp embedded region. Things like
* JSP Declaration can't be embedded.
*/
private boolean isJSPEmbeddedStartOrEnd(IStructuredDocumentRegion flatNode) {
boolean result = false;
String type = flatNode.getType();
result = ((type == DOMJSPRegionContexts.JSP_SCRIPTLET_OPEN) || (type == DOMJSPRegionContexts.JSP_EXPRESSION_OPEN) || (type == DOMJSPRegionContexts.JSP_DECLARATION_OPEN));
return result;
}
/**
* extends super class behavior
*/
protected boolean isPartOfBlockRegion(IStructuredDocumentRegion flatNode) {
boolean result = false;
String type = flatNode.getType();
result = ((type == DOMJSPRegionContexts.JSP_CLOSE) || (type == DOMJSPRegionContexts.JSP_CONTENT) || super.isPartOfBlockRegion(flatNode));
return result;
}
public IStructuredTextReParser newInstance() {
return new JSPReParser();
}
public StructuredDocumentEvent quickCheck() {
if (containsBreakingChange(new CoreNodeList(dirtyStart, dirtyEnd)))
return null;
return super.quickCheck();
}
}