/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* 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:
* Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation
*******************************************************************************/
package org.eclipse.imp.services.base;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.eclipse.imp.core.ErrorHandler;
import org.eclipse.imp.parser.ISourcePositionLocator;
import org.eclipse.imp.parser.IParseController;
import org.eclipse.imp.runtime.RuntimePlugin;
import org.eclipse.imp.services.IFoldingUpdater;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
/**
* FolderBase is an abstract base type for a source-text folding service.
* It is intended to support extensions for language-specific folders.
* The class is abstract only with respect to a method that sends a
* visitor to an AST, as both the visitor and AST node types are language
* specific.
*
* @author suttons@us.ibm.com
* @author rfuhrer@watson.ibm.com
*/
public abstract class FolderBase implements IFoldingUpdater {
// Maps new annotations to positions
protected HashMap<Annotation,Position> newAnnotations = new HashMap<Annotation, Position>();
// Lists the new annotations, which are the keys for newAnnotations
protected List<Annotation> annotations = new ArrayList<Annotation>();
protected IParseController parseController = null;
// Used to support checking of whether annotations have
// changed between invocations of updateFoldingStructure
// (because, if they haven't, then it's probably best not
// to update the folding structure)
private ArrayList<Annotation> oldAnnotationsList = null;
private Annotation[] oldAnnotationsArray;
protected boolean fDebugMode = false;
// Methods to make annotations will typically be called by visitor methods
// in the language-specific concrete subtype
/**
* Make a folding annotation that corresponds to the extent of text
* represented by a given program entity. Usually, this will be an
* AST node, but it can be anything for which the language's
* ISourcePositionLocator can produce an offset/end offset.
*
* @param n an Object representing a program entity
*/
public void makeAnnotation(Object n) {
ISourcePositionLocator nodeLocator = parseController.getSourcePositionLocator();
int startOffset = 0;
int endOffset = 0;
try {
startOffset = nodeLocator.getStartOffset(n);
endOffset = nodeLocator.getEndOffset(n);
} catch (Exception e) {
RuntimePlugin.getInstance().logException("Error while attempting to determine position of a foldable source entity", e);
return;
}
makeAnnotation(startOffset, endOffset-startOffset+1);
}
/**
* Make a folding annotation that corresponds to the given range of text.
*
* @param start The starting offset of the text range
* @param len The length of the text range
*/
public void makeAnnotation(int start, int len) {
if (fDebugMode) {
PrintStream cons= RuntimePlugin.getInstance().getConsoleStream();
cons.println("Adding folding annotation for extent [" + start + ":" + len + "]");
}
ProjectionAnnotation annotation= new ProjectionAnnotation();
newAnnotations.put(annotation, new Position(start, len));
annotations.add(annotation);
}
/**
* Update the folding structure for a source text, where the text and its
* AST are represented by a given parse controller and the folding structure
* is represented by annotations in a given annotation model.
*
* This is the principal routine of the folding updater.
*
* The implementation provided here makes use of a local class
* FoldingUpdateStrategy, to which the task of updating the folding
* structure is delegated.
*
* updateFoldingStructure is synchronized because, at least on file opening,
* it can be called more than once before the first invocation has completed.
* This can lead to inconsistent calculations resulting in the absence of
* folding annotations in newly opened files.
*
* @param parseController A parse controller through which the AST for
* the source text can be accessed
* @param annotationModel A structure of projection annotations that
* represent the foldable elements in the source
* text
*/
public synchronized void updateFoldingStructure(
IParseController parseController, ProjectionAnnotationModel annotationModel)
{
if (fDebugMode) {
PrintStream cons= RuntimePlugin.getInstance().getConsoleStream();
cons.println("Collecting folding annotations");
}
if (parseController != null)
this.parseController = parseController;
try {
Object ast = parseController.getCurrentAst();
if (ast == null) {
// We can't create annotations without an AST
return;
}
// But, since here we have the AST ...
sendVisitorToAST(newAnnotations, annotations, ast);
// Update the annotation model if there have been changes
// but not otherwise (since update leads to redrawing of the
// source in the editor, which is likely to be unwelcome if
// there haven't been any changes relevant to folding)
boolean updateNeeded = false;
if (oldAnnotationsList == null) {
// Should just be the first time through
updateNeeded = true;
} else {
// Check to see whether the current and previous annotations
// differ in any significant way; if not, then there's no
// reason to update the annotation model.
// Note: This test may be implemented in various ways that may
// be more or less simple, efficient, correct, etc. (The
// default test provided below is simplistic although quick and
// usually effective.)
updateNeeded = differ(oldAnnotationsList, annotations);
}
if (updateNeeded) {
// Save the current annotations to compare for changes the next time
oldAnnotationsList = new ArrayList<Annotation>();
for (int i = 0; i < annotations.size(); i++) {
oldAnnotationsList.add(annotations.get(i));
}
} else {
}
// Need to curtail calls to modifyAnnotations() because these lead to calls
// to fireModelChanged(), which eventually lead to calls to updateFoldingStructure,
// which lead back here, which would lead to another call to modifyAnnotations()
// (unless those were curtailed)
if (updateNeeded) {
annotationModel.modifyAnnotations(oldAnnotationsArray, newAnnotations, null);
// Capture the latest set of annotations in a form that can be used the next
// time that it is necessary to modify the annotations
oldAnnotationsArray = (Annotation[]) annotations.toArray(new Annotation[annotations.size()]);
} else {
}
newAnnotations.clear();
annotations.clear();
} catch (Exception e) {
ErrorHandler.reportError("FolderBase.updateFoldingStructure: EXCEPTION", e);
}
}
/**
* A method to test whether there has been a significant change in the folding
* annotations for a source text. The method works by comparing two lists of
* annotations, nominally the "old" and "new" annotations. It returns true iff
* there is considered to be a "significant" difference in the two lists, where
* the meaning of "significant" is defined by the implementation of this method.
*
* The default implementation provided here is a simplistic test of the difference
* between two lists, considering only their size. This may work well enough much
* of the time as the comparisons between lists should be made very frequently,
* actually more frequently than the rate at which the typical human user will
* edit the program text so as to affect the AST so as to affect the lists. Thus
* most changes of lists will entail some change in the number of elements at some
* point that will be observed here. This will not work for certain very rapid
* edits of source text (e.g., rapid replacement of elements).
*
* This method should be overridden in language-specific implementations of the
* folding updater where a more sophisticated test is desired.
*
* @param list1 A list of annotations (nominally the "old" annotations)
* @param list2 A list of annotations (nominally the "new" annotations)
* @return true iff there has been a "significant" difference in the
* two given lists of annotations
*
*/
protected boolean differ(List<Annotation> list1, List<Annotation> list2) {
if (list1.size() != list2.size()) {
return true;
}
return false;
}
/**
* Send a visitor to an AST representing a program in order to construct the
* folding annotations. Both the visitor type and the AST node type are language-
* dependent, so this method is abstract.
*
* @param newAnnotations A map of annotations to text positions
* @param annotations A listing of the annotations in newAnnotations, that is,
* a listing of keys to the map of text positions
* @param ast An Object that will be taken to represent an AST node
*/
protected abstract void sendVisitorToAST(HashMap<Annotation,Position> newAnnotations, List<Annotation> annotations, Object ast);
protected void dumpAnnotations(final List<Annotation> annotations, final HashMap<Annotation,Position> newAnnotations)
{
for(int i= 0; i < annotations.size(); i++) {
Annotation a= (Annotation) annotations.get(i);
Position p= (Position) newAnnotations.get(a);
if (p == null) {
System.out.println("Annotation position is null");
continue;
}
System.out.println("Annotation @ " + p.offset + ":" + p.length);
}
}
}