Package com.googlecode.lambda4jdt

Source Code of com.googlecode.lambda4jdt.JavaStyleClosuresFoldingProvider$JavaImportPosition

/*******************************************************************************
* Copyright (c) 2006, 2008 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 com.googlecode.lambda4jdt;

import static org.eclipse.jdt.core.compiler.ITerminalSymbols.*;
import com.googlecode.lambda4jdt.RegionDemarkator.RegionHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IImportContainer;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.corext.SourceRange;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.text.DocumentCharacterIterator;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider;
import org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.projection.IProjectionListener;
import org.eclipse.jface.text.source.projection.IProjectionPosition;
import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;

/**
* Updates the projection model of a class file or compilation unit.
* <p>
* Clients may instantiate or subclass. Subclasses must make sure to always call the superclass'
* code when overriding methods that are marked with "subclasses may extend".
* </p>
* @since 3.0 (internal)
* @since 3.2 (API)
*/
@SuppressWarnings({"restriction","unused"})
public class JavaStyleClosuresFoldingProvider implements IJavaFoldingStructureProvider,
        IJavaFoldingStructureProviderExtension {

  private static final String FUNCTION_RELATION_TOKEN = "/* => */";

  private static final String FUNCTION_RELATION_TOKEN_ONELINE = "// => ";

  /**
   * A context that contains the information needed to compute the folding structure of an
   * {@link ICompilationUnit} or an {@link IClassFile}. Computed folding regions are collected via
   * {@linkplain #addProjectionRange(JavaStyleClosuresFoldingProvider.JavaProjectionAnnotation, Position)
   * addProjectionRange}.
   */
  protected final class FoldingStructureComputationContext {
    private final ProjectionAnnotationModel fModel;
    private final IDocument fDocument;

    private final boolean fAllowCollapsing;

    private IType fFirstType;
    private boolean fHasHeaderComment;
    private LinkedHashMap<JavaProjectionAnnotation, Position> fMap = new LinkedHashMap<JavaProjectionAnnotation, Position>();
    private IScanner fScanner;
    boolean initial;

    private FoldingStructureComputationContext(IDocument document,
            ProjectionAnnotationModel model, boolean allowCollapsing, IScanner scanner) {
      Assert.isNotNull(document);
      Assert.isNotNull(model);
      fDocument = document;
      fModel = model;
      fAllowCollapsing = allowCollapsing;
      fScanner = scanner;
    }

    private void setFirstType(IType type) {
      if (hasFirstType())
        throw new IllegalStateException();
      fFirstType = type;
    }

    boolean hasFirstType() {
      return fFirstType != null;
    }

    private IType getFirstType() {
      return fFirstType;
    }

    private boolean hasHeaderComment() {
      return fHasHeaderComment;
    }

    private void setHasHeaderComment() {
      fHasHeaderComment = true;
    }

    /**
     * Returns <code>true</code> if newly created folding regions may be collapsed,
     * <code>false</code> if not. This is usually <code>false</code> when updating the folding
     * structure while typing; it may be <code>true</code> when computing or restoring the
     * initial folding structure.
     * @return <code>true</code> if newly created folding regions may be collapsed,
     *         <code>false</code> if not
     */
    public boolean allowCollapsing() {
      return fAllowCollapsing;
    }

    /**
     * Returns the document which contains the code being folded.
     * @return the document which contains the code being folded
     */
    private IDocument getDocument() {
      return fDocument;
    }

    private ProjectionAnnotationModel getModel() {
      return fModel;
    }

    private IScanner getScanner() {
      if (fScanner == null)
        fScanner = ToolFactory.createScanner(true, false, false, false);
      return fScanner;
    }

    /**
     * Adds a projection (folding) region to this context. The created annotation / position
     * pair will be added to the {@link ProjectionAnnotationModel} of the
     * {@link ProjectionViewer} of the editor.
     * @param annotation the annotation to add
     * @param position the corresponding position
     */
    public void addProjectionRange(JavaProjectionAnnotation annotation, Position position) {
      fMap.put(annotation, position);
    }

    /**
     * Returns <code>true</code> if header comments should be collapsed.
     * @return <code>true</code> if header comments should be collapsed
     */
    public boolean collapseHeaderComments() {
      return fAllowCollapsing && fCollapseHeaderComments;
    }

    /**
     * Returns <code>true</code> if import containers should be collapsed.
     * @return <code>true</code> if import containers should be collapsed
     */
    public boolean collapseImportContainer() {
      return fAllowCollapsing && fCollapseImportContainer;
    }

    /**
     * Returns <code>true</code> if inner types should be collapsed.
     * @return <code>true</code> if inner types should be collapsed
     */
    public boolean collapseInnerTypes() {
      return fAllowCollapsing && fCollapseInnerTypes;
    }

    /**
     * Returns <code>true</code> if javadoc comments should be collapsed.
     * @return <code>true</code> if javadoc comments should be collapsed
     */
    public boolean collapseJavadoc() {
      return fAllowCollapsing && fCollapseJavadoc;
    }

    /**
     * Returns <code>true</code> if methods should be collapsed.
     * @return <code>true</code> if methods should be collapsed
     */
    public boolean collapseMembers() {
      return fAllowCollapsing && fCollapseMembers;
    }
  }

  private Map<IType, Integer> annotationDecorationDrawingOffsets = new WeakHashMap<IType, Integer>();

  /**
   * A {@link ProjectionAnnotation} for java code.
   */
  protected final class JavaProjectionAnnotation extends ProjectionAnnotation implements
          AnnotationPainter.IDrawingStrategy {

    private IJavaElement fJavaElement;
    private boolean fIsComment;
    private final boolean isCollapsedByDefault;

    /**
     * Creates a new projection annotation.
     * @param isCollapsed <code>true</code> to set the initial state to collapsed,
     *            <code>false</code> to set it to expanded
     * @param ctx
     * @param element the java element this annotation refers to
     * @param isComment <code>true</code> for a foldable comment, <code>false</code> for a
     *            foldable code element
     */
    public JavaProjectionAnnotation(boolean isCollapsed,
            FoldingStructureComputationContext ctx, IJavaElement element, boolean isComment) {
      super(isCollapsed);
      this.isCollapsedByDefault = isCollapsed;
      setElement(element);
      fIsComment = isComment;
    }

    IJavaElement getElement() {
      return fJavaElement;
    }

    void setElement(IJavaElement element) {
      fJavaElement = element;
      closurable = findLambdaMethodIn(fJavaElement) != null;
    }

    boolean isComment() {
      return fIsComment;
    }

    void setIsComment(boolean isComment) {
      fIsComment = isComment;
    }

    /*
     * @see java.lang.Object#toString()
     */
    public String toString() {
      return "JavaProjectionAnnotation:\n" + //$NON-NLS-1$
              "\telement: \t" + fJavaElement.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
              "\tcollapsed: \t" + isCollapsed() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
              "\tcomment: \t" + isComment() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
    }

    public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset,
            int length, Color color) {

      if (fIsComment || !closurable)
        throw new UnsupportedOperationException();

      // TEMP
      if (isCollapsed() || isCollapsedByDefault) {
        if (gc != null) {
          // Do not draw anything
        } else
          textWidget.redrawRange(offset, length, true);
      }
    }
    boolean closurable;

  }

  static IMethod findLambdaMethodIn(IJavaElement element) {
    try {
      if (element.getElementType() == IJavaElement.TYPE) {
        IType t = (IType) element;
        if (t.isAnonymous()) {
          IJavaElement[] c = t.getChildren();
          if (c.length == 1 && c[0].getElementType() == IJavaElement.METHOD) {
            IMethod m = (IMethod) c[0];
            String source = m.getSource();
// boolean hasToken1 = source.contains(FUNCTION_RELATION_TOKEN);
// boolean hasToken2 = source.contains(FUNCTION_RELATION_TOKEN_ONELINE);
// if (hasToken1 ^ hasToken2)
            return m;
// IAnnotation[] annotations = m.getAnnotations();
// if (annotations != null) {
// for (IAnnotation annotation : annotations) {
// if (annotation.getElementName().endsWith("FunctionFolded"))
// return m;
// }
// }
          }
        }
      }
    } catch (Exception e) {}
    return null;
  }

/*  private static final IProgressMonitor noMonitor = new NoProgress();*/

  private static final class Tuple {
    JavaProjectionAnnotation annotation;
    Position position;

    Tuple(JavaProjectionAnnotation annotation, Position position) {
      this.annotation = annotation;
      this.position = position;
    }
  }

  /**
   * Filter for annotations.
   */
  private static interface Filter {
    boolean match(JavaProjectionAnnotation annotation);
  }

  /**
   * Matches comments.
   */
  private static final class CommentFilter implements Filter {
    public boolean match(JavaProjectionAnnotation annotation) {
      if (annotation.isComment() && !annotation.isMarkedDeleted()) {
        return true;
      }
      return false;
    }
  }

  /**
   * Matches members.
   */
  private static final class MemberFilter implements Filter {
    public boolean match(JavaProjectionAnnotation annotation) {
      if (!annotation.isComment() && !annotation.isMarkedDeleted()) {
        IJavaElement element = annotation.getElement();
        if (element instanceof IMember) {
          if (element.getElementType() != IJavaElement.TYPE ||
                  ((IMember) element).getDeclaringType() != null) {
            return true;
          }
        }
      }
      return false;
    }
  }

  /**
   * Matches java elements contained in a certain set.
   */
  private static final class JavaElementSetFilter implements Filter {
    private final Set<? extends IJavaElement> set;
    private final boolean matchCollapsed;

    private JavaElementSetFilter(Set<? extends IJavaElement> set, boolean matchCollapsed) {
      this.set = set;
      this.matchCollapsed = matchCollapsed;
    }

    public boolean match(JavaProjectionAnnotation annotation) {
      boolean stateMatch = matchCollapsed == annotation.isCollapsed();
      if (stateMatch && !annotation.isComment() && !annotation.isMarkedDeleted()) {
        IJavaElement element = annotation.getElement();
        if (set.contains(element)) {
          return true;
        }
      }
      return false;
    }
  }

  private class ElementChangedListener implements IElementChangedListener {

    /*
     * @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
     */
    public void elementChanged(ElementChangedEvent e) {
      IJavaElementDelta delta = findElement(fInput, e.getDelta());
      if (delta != null &&
              (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_CHILDREN)) != 0) {

        CompilationUnit unitAST = e.getDelta().getCompilationUnitAST();
        if (shouldIgnoreDelta(unitAST, delta))
          return;

        fUpdatingCount++;
        try {
          update(createContext(false));
        } finally {
          fUpdatingCount--;
        }
      }
    }

    /**
     * Ignore the delta if there are errors on the caret line.
     * <p>
     * We don't ignore the delta if an import is added and the caret isn't inside the import
     * container.
     * </p>
     * @param ast the compilation unit AST
     * @param delta the Java element delta for the given AST element
     * @return <code>true</code> if the delta should be ignored
     * @since 3.3
     */
    private boolean shouldIgnoreDelta(CompilationUnit ast, IJavaElementDelta delta) {
      if (ast == null)
        return false; // can't compute

      IDocument document = getDocument();
      if (document == null)
        return false; // can't compute

      JavaEditor editor = fEditor;
      Point selectedRange = null;
      if (editor == null || (selectedRange = editor.getCachedSelectedRange()) == null)
        return false; // can't compute

      try {
        IJavaElementDelta[] affectedChildren = delta.getAffectedChildren();
        if (affectedChildren.length == 1 &&
                affectedChildren[0].getElement() instanceof IImportContainer) {
          IJavaElement elem = SelectionConverter.getElementAtOffset(ast.getTypeRoot(),
                  new TextSelection(selectedRange.x, selectedRange.y));
          if (!(elem instanceof IImportDeclaration))
            return false;

        }
      } catch (JavaModelException e) {
        return false; // can't compute
      }

      int caretLine = 0;
      try {
        caretLine = document.getLineOfOffset(selectedRange.x) + 1;
      } catch (BadLocationException x) {
        return false; // can't compute
      }

      if (caretLine > 0) {
        IProblem[] problems = ast.getProblems();
        for (int i = 0; i < problems.length; i++) {
          if (problems[i].isError() && caretLine == problems[i].getSourceLineNumber())
            return true;
        }
      }

      return false;
    }

    private IJavaElementDelta findElement(IJavaElement target, IJavaElementDelta delta) {

      if (delta == null || target == null)
        return null;

      IJavaElement element = delta.getElement();

      if (element.getElementType() > IJavaElement.CLASS_FILE)
        return null;

      if (target.equals(element))
        return delta;

      IJavaElementDelta[] children = delta.getAffectedChildren();

      for (int i = 0; i < children.length; i++) {
        IJavaElementDelta d = findElement(target, children[i]);
        if (d != null)
          return d;
      }

      return null;
    }
  }

  /**
   * Projection position that will return two foldable regions: one folding away the region from
   * after the '/**' to the beginning of the content, the other from after the first content line
   * until after the comment.
   */
  private static final class JavaCommentPosition extends Position implements IProjectionPosition {
    JavaCommentPosition(int offset, int length) {
      super(offset, length);
    }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
     */
    public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
// if (0 == 0)
// return new IRegion[] { new Region(offset, length) };
//     
      DocumentCharacterIterator sequence = new DocumentCharacterIterator(document, offset,
              offset + length);
      int prefixEnd = 0;
      int contentStart = findFirstContent(sequence, prefixEnd);

      int firstLine = document.getLineOfOffset(offset + prefixEnd);
      int captionLine = document.getLineOfOffset(offset + contentStart);
      int lastLine = document.getLineOfOffset(offset + length);

      Assert.isTrue(firstLine <= captionLine,
              "first folded line is greater than the caption line"); //$NON-NLS-1$
      Assert.isTrue(captionLine <= lastLine,
              "caption line is greater than the last folded line"); //$NON-NLS-1$

      IRegion preRegion = null, postRegion = null;
      if (firstLine < captionLine) {
// preRegion= new Region(offset + prefixEnd, contentStart - prefixEnd);
        int preOffset = document.getLineOffset(firstLine);
        IRegion preEndLineInfo = document.getLineInformation(captionLine);
        int preEnd = preEndLineInfo.getOffset();
        preRegion = new Region(preOffset, preEnd - preOffset);
      }

      if (captionLine < lastLine) {
        int postOffset = document.getLineOffset(captionLine + 1);
        postRegion = new Region(postOffset, offset + length - postOffset);
      }

      return combineRegions(preRegion, postRegion);
    }

    /**
     * Finds the offset of the first identifier part within <code>content</code>. Returns 0 if
     * none is found.
     * @param content the content to search
     * @param prefixEnd the end of the prefix
     * @return the first index of a unicode identifier part, or zero if none can be found
     */
    private int findFirstContent(final CharSequence content, int prefixEnd) {
      int lenght = content.length();
      for (int i = prefixEnd; i < lenght; i++) {
        if (Character.isUnicodeIdentifierPart(content.charAt(i)))
          return i;
      }
      return 0;
    }

// /**
// * Finds the offset of the first identifier part within <code>content</code>.
// * Returns 0 if none is found.
// *
// * @param content the content to search
// * @return the first index of a unicode identifier part, or zero if none can
// * be found
// */
// private int findPrefixEnd(final CharSequence content) {
// // return the index after the leading '/*' or '/**'
// int len= content.length();
// int i= 0;
// while (i < len && isWhiteSpace(content.charAt(i)))
// i++;
// if (len >= i + 2 && content.charAt(i) == '/' && content.charAt(i + 1) == '*')
// if (len >= i + 3 && content.charAt(i + 2) == '*')
// return i + 3;
// else
// return i + 2;
// else
// return i;
// }
//
// private boolean isWhiteSpace(char c) {
// return c == ' ' || c == '\t';
// }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
     */
    public int computeCaptionOffset(IDocument document) {
// return offset;
      try {
        DocumentCharacterIterator sequence = new DocumentCharacterIterator(document,
                offset, offset + length);

        return findFirstContent(sequence, 0);
      } catch (BadLocationException e) {
        return offset;
      }

    }
  }

  protected static IRegion[] combineRegions(IRegion preRegion, IRegion postRegion) {
    if (preRegion != null && postRegion != null)
      return new IRegion[] { preRegion, postRegion };

    if (preRegion != null)
      return new IRegion[] { preRegion };

    if (postRegion != null)
      return new IRegion[] { postRegion };

    return null;
  }

  /**
   * Projection position that will return two foldable regions: one folding away the lines before
   * the one containing the simple name of the java element, one folding away any lines after the
   * caption.
   */
  private final class JavaElementPosition extends Position implements IProjectionPosition {

    private IMember fMember;
    private IMethod lambdaMethod;

// private IRegion[] cachedStructure;
// private String cachedSourceForCheck;

    public JavaElementPosition(int offset, int length, IMember member, IMethod lambdaMethod) {
      super(offset, length);
      Assert.isNotNull(member);
      fMember = member;
      this.lambdaMethod = lambdaMethod;
    }

    public void setMember(IMember member) {
      Assert.isNotNull(member);
      fMember = member;
    }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeFoldingRegions(org.eclipse.jface.text.IDocument)
     */
    public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
      if (!fMember.exists())
        return null;

      int nameStart = computeCaptionOffset(document) + offset;

      try {
        ISourceRange sourceRange = fMember.getSourceRange();
        IMethod foldedMethod = lambdaMethod;

        if (foldedMethod != null && !foldedMethod.exists()) {
          foldedMethod = findLambdaMethodIn(fMember);
          if (foldedMethod != null) {
            lambdaMethod = foldedMethod;
          }
        }

        if (foldedMethod != null && sourceRange != null)
          return computeFunctionRegions(document, sourceRange, null);
      } catch (IllegalStateException e) {
        System.out.println(e.getMessage());
      } catch (UnsupportedOperationException e) {

      } catch (Exception e) {
        e.printStackTrace();
        return null;
      } catch (AssertionError e) {
        e.printStackTrace();
      }

      int firstLine = document.getLineOfOffset(offset);
      int captionLine = document.getLineOfOffset(nameStart);
      int lastLine = document.getLineOfOffset(offset + length);

      /* see comment above - adjust the caption line to be inside the
       * entire folded region, and rely on later element deltas to correct
       * the name range. */
      if (captionLine < firstLine)
        captionLine = firstLine;
      if (captionLine > lastLine)
        captionLine = lastLine;

      IRegion preRegion = null, postRegion = null;
      if (firstLine < captionLine) {
        int preOffset = document.getLineOffset(firstLine);
        IRegion preEndLineInfo = document.getLineInformation(captionLine);
        int preEnd = preEndLineInfo.getOffset();
        preRegion = new Region(preOffset, preEnd - preOffset);
      }

      if (captionLine < lastLine) {
        int postOffset = document.getLineOffset(captionLine + 1);
        postRegion = new Region(postOffset, offset + length - postOffset);
      }

      return combineRegions(preRegion, postRegion);
    }

    private IRegion[] computeFunctionRegions(IDocument document, ISourceRange sourceRange,
            RegionDemarkator editables) throws BadLocationException {

// String sat = null;
//     
// try {
// sat = ((IType) foldedMethod.getParent()).getSource();
// sourceRange = ((IType) foldedMethod.getParent()).getSourceRange();
// } catch (JavaModelException e1) {}
//
      int anonymousTypeOffset = sourceRange.getOffset();
      int anonymousTypeLength = sourceRange.getLength();

      String anomymousTypeSource = document.get(anonymousTypeOffset, anonymousTypeLength);

      // String anomymousTypeSource = foldedMethod.getSource();
// if (cachedStructure != null && anomymousTypeSource.equals(cachedSourceForCheck))
// return cachedStructure;

// int functionMethodOffset = foldedMethod.getSourceRange().getOffset();

      if (editables != null) {
        editables.initialOffset = anonymousTypeOffset;
      }

      RegionDemarkator marker = new RegionDemarkator();
      marker.initialOffset = anonymousTypeOffset;
      marker.start(0);

      ScannerHelper scan = new ScannerHelper(anomymousTypeSource);

      scan.seekCorresponding(TokenNameLBRACE);
      int f = scan.offset;

      marker.end(f);
      marker.start(f);

      RegionHandle openingBraceRegion = marker.end(++f);
      marker.start(f);

/*      // 'public'
      scan.seek(TokenNamepublic);
      f = scan.endOffset + 1;
      marker.end(f);
      marker.start(f);
      RegionHandle afterOpeningBraceWhitespace = marker.end(++f);
*/
// show(document, afterOpeningBraceWhitespace);
      // Seek parenthese opening parameter list
      scan.seekCorresponding(TokenNameLPAREN);

      int parameterListOpenParenOffset = scan.offset;

      marker.end(parameterListOpenParenOffset);
      marker.start(parameterListOpenParenOffset);
      RegionHandle parameterListOpenParen = marker.end(parameterListOpenParenOffset + 1);
      marker.start(parameterListOpenParenOffset + 1);
// assert scan.identifier != null;
//
// int methodNameIdentifierOffset = scan.identifierOffset;
//
// marker.end(methodNameIdentifierOffset - 1);
// // marker.start(methodNameIdentifierOffset - 1);
// // marker.end(methodNameIdentifierOffset);
//
// marker.start(methodNameIdentifierOffset);

      int paramsCount = 0;

      for (;;) {
        int t = scan.seekCorrespondingWithTypeParameterBrackets(TokenNameCOMMA,
                TokenNameRPAREN);
        if (scan.identifier != null) {
          paramsCount++;
          marker.end(scan.identifierOffset);

          if (editables != null) {
            editables.start(scan.identifierOffset);
            editables.end(scan.identifierOffset + scan.identifier.length);
          }

          if (t == TokenNameCOMMA) {
            scan.seekAnyExcept(TokenNameWHITESPACE);
            marker.start(scan.offset);
          }
        }

        if (t == TokenNameRPAREN || t < 0) {
          break;
        }
      }

      int rparenOffset = scan.offset;

      if (!marker.started)
        marker.start(rparenOffset);

      RegionHandle parameterListClosingParen = marker.end(rparenOffset + 1);

      marker.start(rparenOffset + 1);

      int lastPreMethodOffset = rparenOffset + 1;

// // To show functional arrow `=>`
// int arrowIndex = functionTokenIndex + 2;
// RegionHandle preArrowRegion = marker.end(arrowIndex);
// marker.start(arrowIndex);
//
// RegionHandle arrowRegion = marker.end(functionTokenIndex + 6);
// marker.start(functionTokenIndex + 6);

// annotationDecorationDrawingOffsets.put((IType) fMember, anonymousTypeOffset +
// functionTokenIndex + 3);

      scan.seekCorresponding(TokenNameLBRACE);

// // Start searching for lines a new
// scan.lineEnds.clear();

      int methodOpeningBraceOffset = scan.offset;

      // find where we go past all throws declaration if any
      if (scan.identifierOffset > lastPreMethodOffset) {
        lastPreMethodOffset = scan.identifierOffset + scan.identifier.length;
        while (anomymousTypeSource.charAt(lastPreMethodOffset) == ' ') {
          lastPreMethodOffset++;
        }
      }

      int indexOfArrowComment = anomymousTypeSource.indexOf(FUNCTION_RELATION_TOKEN);

      if (indexOfArrowComment >= lastPreMethodOffset &&
              indexOfArrowComment < methodOpeningBraceOffset)
        lastPreMethodOffset = indexOfArrowComment + FUNCTION_RELATION_TOKEN.length();

      int oneLineTokenIndex = anomymousTypeSource.indexOf(FUNCTION_RELATION_TOKEN_ONELINE);
      if (oneLineTokenIndex >= lastPreMethodOffset) {
        do {
          char c = anomymousTypeSource.charAt(oneLineTokenIndex);
          if (c == '\n' || c == '\r')
            break;

          oneLineTokenIndex++;
        } while (oneLineTokenIndex < methodOpeningBraceOffset);
        lastPreMethodOffset = oneLineTokenIndex;
      }

      // and hide it

      boolean hasPreBraceRegion = lastPreMethodOffset < methodOpeningBraceOffset;
      if (hasPreBraceRegion) {
        marker.end(lastPreMethodOffset);
        marker.start(lastPreMethodOffset);
// preBraceRegion = marker.end(methodOpeningBraceOffset);
// marker.start(methodOpeningBraceOffset);
      }

      RegionHandle preBraceRegion = marker.end(methodOpeningBraceOffset);
      marker.start(methodOpeningBraceOffset);
      RegionHandle methodOpeningBrace = marker.end(methodOpeningBraceOffset + 1);

      int methodCloseBraceOffset = -1;
      int returnOffset = -1;
      boolean singleStatement = true;
      int statementTerminator = -1;
      int firstNonWhitespace = -1;
      int lastBraceBlockEnd = -1;

      bodyScanLoop: for (;;) {
        int t = scan
                .seekCorresponding(TokenNameSEMICOLON, TokenNameRBRACE, TokenNamereturn);

        if (scan.wasFlowControlStatement)
          singleStatement = false;

        lastBraceBlockEnd = scan.lastBraceBlockEnd;

        if (firstNonWhitespace < 0)
          firstNonWhitespace = scan.firstNonWhitespace;

        swicher: switch (t) {
        case TokenNameSEMICOLON:
          if (statementTerminator > 0 && singleStatement)
            singleStatement = false;

          statementTerminator = scan.offset;
          continue bodyScanLoop;

        case TokenNamereturn:
          returnOffset = scan.offset;
          continue bodyScanLoop;

        case TokenNameRBRACE:
          methodCloseBraceOffset = scan.offset;
        }

        break;
      }

      if (lastBraceBlockEnd > statementTerminator) {
        statementTerminator = lastBraceBlockEnd;
        singleStatement = false;
      }

      boolean useClauseFolding = wheretherAnonymousTypeIsSingleParameterToHigherOrderMethod(
              document, anonymousTypeOffset, anonymousTypeLength);

      if (singleStatement && !(useClauseFolding && returnOffset < 0)) {

        if (paramsCount > 0) {
          parameterListOpenParen.reveal();// Show opening parameter paren
          parameterListClosingParen.reveal();// Show closing parameter paren
        }

        // Show up method openingBrace
        methodOpeningBrace.reveal();

        if (returnOffset > 0) {

          marker.start(methodOpeningBraceOffset + 1); // from after opening of method body
          marker.end(returnOffset + 7); // to end of return keyword

          marker.start(statementTerminator);// From before last semicolon
          marker.end(anonymousTypeLength - 1);// to end on anonymous type definition

        } else if (lastBraceBlockEnd < 0 && statementTerminator < 0) {

          marker.start(methodOpeningBraceOffset + 1); // from after opening of method body
          marker.end(anonymousTypeLength - 1);// to end on anonymous type definition

        } else {
          // openingBraceRegion.reveal();// Show up openingBrace
          marker.start(methodOpeningBraceOffset + 1); // from after opening of method body
          marker.end(firstNonWhitespace);// to begin of statement

          marker.start(statementTerminator + 1);// From before last semicolon
          marker.end(anonymousTypeLength - 1);// to end on anonymous type definition
          // marker.start(statementTerminator + 1);// From after last semicolon
          // marker.end(anonymousTypeLength - 1); // to before final closing brace
        }
      } else {

        if (paramsCount > 0) {
          parameterListOpenParen.reveal();// Show opening parameter paren
          parameterListClosingParen.reveal();// Show closing parameter paren
        }

        if (hasPreBraceRegion) {
          preBraceRegion.reveal();
        }

        // Show up method openingBrace
        methodOpeningBrace.reveal();

// marker.start(methodOpeningBraceOffset); // from before opening of method body
// marker.end(methodOpeningBraceOffset + 1);

// int methodRightBraceLine = document.getLineOfOffset(anonymousTypeOffset +
// methodCloseBraceOffset);
//
// int anonymousTypeBraceLine = document.getLineOfOffset(anonymousTypeOffset +
// anonymousTypeLength);

        if (statementTerminator < 0) {
          statementTerminator = methodCloseBraceOffset - 2;
        }

        marker.start(statementTerminator + 1); // hide lines from body end
        // to anonymous class end (exclusive)
        marker.end(methodCloseBraceOffset + 1);

        eatUpExtraTabsOnEachLine(anomymousTypeSource, marker, scan, lastPreMethodOffset,
                methodCloseBraceOffset);

        if (useClauseFolding)
          try {

// arrowRegion.offset++;
// arrowRegion.length--;
            // arrowRegion.reveal();// ?

// if (editables != null)
// editables.clearAll();

// if (paramsCount == 0) {
// parameterListOpenParen.reveal();// Show opening parameter paren
// parameterListClosingParen.reveal();// Show closing parameter paren
// }
            // marker.printRegionHandles(document);

            Region prefix = new Region(anonymousTypeOffset - 1, 1);
            Region suffix = new Region(anonymousTypeOffset + anonymousTypeLength, 2);
            return marker.toProcessedArray(prefix, suffix);

          } catch (Exception e) {
            e.printStackTrace();
          }
      }

      // eat leading space before arror `=>` when no parameters
// if (paramsCount == 0) {
// preArrowRegion.length++;
// arrowRegion.offset++;
// arrowRegion.length--;
// }
//
// arrowRegion.reveal();

      IRegion[] resultingArray = marker.toProcessedArray();

      // System.out.println("__ " + Arrays.toString(resultingArray));

// Cache for later use
// this.cachedStructure = resultingArray;
// this.cachedSourceForCheck = anomymousTypeSource;

      return resultingArray;
    }

    private void eatUpExtraTabsOnEachLine(String anomymousTypeSource, RegionDemarkator marker,
            ScannerHelper scan, int startOffset, int endOffset) {
      for (int index : scan.lineEnds()) {
        if (index < startOffset || index > endOffset)
          continue;
        char startingNextLine = anomymousTypeSource.charAt(index + 1);
        if (startingNextLine == '\t') {
          marker.start(index + 1);
          marker.end(index + 2);
        } else {
          boolean hasTabInFormOf4Spaces = true;
          for (int i = index + 1; i < index + 5; i++) {
            if (anomymousTypeSource.charAt(i) != ' ') {
              hasTabInFormOf4Spaces = false;
              break;
            }
          }
          if (hasTabInFormOf4Spaces) {
            marker.start(index + 1);
            marker.end(index + 5);
          }
        }
      }
    }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionPosition#computeCaptionOffset(org.eclipse.jface.text.IDocument)
     */
    public int computeCaptionOffset(IDocument document) throws BadLocationException {
      int nameStart = offset;
      try {
        /* The member's name range may not be correct. However,
         * reconciling would trigger another element delta which would
         * lead to reentrant situations. Therefore, we optimistically
         * assume that the name range is correct, but double check the
         * received lines below. */
        ISourceRange nameRange = fMember.getNameRange();
        if (nameRange != null)
          nameStart = nameRange.getOffset();

      } catch (JavaModelException e) {
        // ignore and use default
      }

      return nameStart - offset;
    }

    /**
     * Not a real comparator, just ugly hack. Not used now
     */
    @SuppressWarnings("unchecked")
    public int compare(Object o1, Object o2) {
      IDocument document = (IDocument) o1;
      Collection<IRegion> editableRegions = (Collection<IRegion>) o2;

      if (!fMember.exists())
        return 0;

      try {
        ISourceRange sourceRange = fMember.getSourceRange();
        IMethod foldedMethod = lambdaMethod;

        if (foldedMethod != null && !foldedMethod.exists()) {
          foldedMethod = findLambdaMethodIn(fMember);
          if (foldedMethod != null) {
            lambdaMethod = foldedMethod;
          }
        }

        if (foldedMethod != null && sourceRange != null) {
          // RegionDemarkator editableRegionDemarkator = new RegionDemarkator();
          computeFunctionRegions(document, sourceRange, null);

// String s = "EDITABLE ";
// for (IRegion region : editableRegionDemarkator.toProcessedArray()) {
// s += "[" + document.get(region.getOffset(), region.getLength()) + "]";
// editableRegions.add(region);
// }
//
// System.out.println(s);
        }
      } catch (UnsupportedOperationException e) {} catch (Exception e) {
        e.printStackTrace();
      } catch (AssertionError e) {
        e.printStackTrace();
      }

      return editableRegions.size();
    }

  }

  private static boolean wheretherAnonymousTypeIsSingleParameterToHigherOrderMethod(
          IDocument document, int declarationOffset, int declarationLength)
          throws BadLocationException {
    return document.get(declarationOffset - 1, 4).equals("(new") &&
            document.get(declarationOffset + declarationLength - 1, 3).equals("});");
  }

  /**
   * Internal projection listener.
   */
  private final class ProjectionListener implements IProjectionListener {
    private ProjectionViewer fViewer;

    /**
     * Registers the listener with the viewer.
     * @param viewer the viewer to register a listener with
     */
    public ProjectionListener(ProjectionViewer viewer) {
      Assert.isLegal(viewer != null);
      fViewer = viewer;
      fViewer.addProjectionListener(this);
    }

    /**
     * Disposes of this listener and removes the projection listener from the viewer.
     */
    public void dispose() {
      if (fViewer != null) {
        fViewer.removeProjectionListener(this);
        fViewer = null;
      }
    }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionEnabled()
     */
    public void projectionEnabled() {
      handleProjectionEnabled();
    }

    /*
     * @see org.eclipse.jface.text.source.projection.IProjectionListener#projectionDisabled()
     */
    public void projectionDisabled() {
      handleProjectionDisabled();
    }
  }

  /* context and listeners */
  private JavaEditor fEditor;
  private ProjectionListener fProjectionListener;
  private IJavaElement fInput;
  private IElementChangedListener fElementListener;

  /* preferences */
  private boolean fCollapseJavadoc = false;
  private boolean fCollapseImportContainer = true;
  private boolean fCollapseInnerTypes = true;
  private boolean fCollapseMembers = false;
  private boolean fCollapseHeaderComments = true;

  /* filters */
  /** Member filter, matches nested members (but not top-level types). */
  private final Filter fMemberFilter = new MemberFilter();
  /** Comment filter, matches comments. */
  private final Filter fCommentFilter = new CommentFilter();

  /**
   * Reusable scanner.
   * @since 3.3
   */
  private IScanner fSharedScanner = ToolFactory.createScanner(true, false, false, false);

  private volatile int fUpdatingCount = 0;
  private ProjectionViewer viewer;

  /**
   * Creates a new folding provider. It must be {@link #install(ITextEditor, ProjectionViewer)
   * installed} on an editor/viewer pair before it can be used, and {@link #uninstall()
   * uninstalled} when not used any longer.
   * <p>
   * The projection state may be reset by calling {@link #initialize()}.
   * </p>
   */
  public JavaStyleClosuresFoldingProvider() {}

  /**
   * {@inheritDoc}
   * <p>
   * Subclasses may extend.
   * </p>
   * @param editor {@inheritDoc}
   * @param viewer {@inheritDoc}
   */
  public void install(ITextEditor editor, ProjectionViewer viewer) {
    this.viewer = viewer;
    Assert.isLegal(editor != null);
    Assert.isLegal(viewer != null);

    internalUninstall();

    if (editor instanceof JavaEditor) {
      fProjectionListener = new ProjectionListener(viewer);
      fEditor = (JavaEditor) editor;
    }
  }

  /**
   * {@inheritDoc}
   * <p>
   * Subclasses may extend.
   * </p>
   */
  public void uninstall() {
    internalUninstall();
  }

  /**
   * Internal implementation of {@link #uninstall()}.
   */
  private void internalUninstall() {
    if (isInstalled()) {
      handleProjectionDisabled();
      fProjectionListener.dispose();
      fProjectionListener = null;
      fEditor = null;
    }
  }

  /**
   * Returns <code>true</code> if the provider is installed, <code>false</code> otherwise.
   * @return <code>true</code> if the provider is installed, <code>false</code> otherwise
   */
  protected final boolean isInstalled() {
    return fEditor != null;
  }

  /**
   * Called whenever projection is enabled, for example when the viewer issues a
   * {@link IProjectionListener#projectionEnabled() projectionEnabled} message. When the provider
   * is already enabled when this method is called, it is first
   * {@link #handleProjectionDisabled() disabled}.
   * <p>
   * Subclasses may extend.
   * </p>
   */
  protected void handleProjectionEnabled() {
    // http://home.ott.oti.com/teams/wswb/anon/out/vms/index.html
    // projectionEnabled messages are not always paired with projectionDisabled
    // i.e. multiple enabled messages may be sent out.
    // we have to make sure that we disable first when getting an enable
    // message.
    handleProjectionDisabled();

    if (isInstalled()) {
      initialize();
      fElementListener = new ElementChangedListener();
      JavaCore.addElementChangedListener(fElementListener);
    }
  }

  /**
   * Called whenever projection is disabled, for example when the provider is {@link #uninstall()
   * uninstalled}, when the viewer issues a {@link IProjectionListener#projectionDisabled()
   * projectionDisabled} message and before {@link #handleProjectionEnabled() enabling} the
   * provider. Implementations must be prepared to handle multiple calls to this method even if
   * the provider is already disabled.
   * <p>
   * Subclasses may extend.
   * </p>
   */
  protected void handleProjectionDisabled() {
    if (fElementListener != null) {
      JavaCore.removeElementChangedListener(fElementListener);
      fElementListener = null;
    }
  }

  /*
   * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProvider#initialize()
   */
  public final void initialize() {
    fUpdatingCount++;
    try {
      update(createInitialContext());
    } finally {
      fUpdatingCount--;
    }
  }

  private FoldingStructureComputationContext createInitialContext() {
    initializePreferences();
    fInput = getInputElement();
    if (fInput == null)
      return null;

    FoldingStructureComputationContext context = createContext(true);
    context.initial = true;
    return context;
  }

  private FoldingStructureComputationContext createContext(boolean allowCollapse) {
    if (!isInstalled())
      return null;
    ProjectionAnnotationModel model = getModel();
    if (model == null)
      return null;
    IDocument doc = getDocument();
    if (doc == null)
      return null;

    IScanner scanner = null;
    if (fUpdatingCount == 1)
      scanner = fSharedScanner; // reuse scanner

    return new FoldingStructureComputationContext(doc, model, allowCollapse, scanner);
  }

  private IJavaElement getInputElement() {
    if (fEditor == null)
      return null;
    return EditorUtility.getEditorInputJavaElement(fEditor, false);
  }

  private void initializePreferences() {
    IPreferenceStore store = JavaPlugin.getDefault().getPreferenceStore();
    fCollapseInnerTypes = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_INNERTYPES);
    fCollapseImportContainer = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_IMPORTS);
    fCollapseJavadoc = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_JAVADOC);
    fCollapseMembers = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_METHODS);
    fCollapseHeaderComments = store.getBoolean(PreferenceConstants.EDITOR_FOLDING_HEADERS);
  }

  private void update(FoldingStructureComputationContext ctx) {
    if (ctx == null)
      return;

    Map<Annotation, Position> additions = new HashMap<Annotation, Position>();
    List<Annotation> deletions = new ArrayList<Annotation>();
    List<Annotation> updates = new ArrayList<Annotation>();

    computeFoldingStructure(ctx);
    Map newStructure = ctx.fMap;
    Map oldStructure = computeCurrentStructure(ctx);

    Iterator e = newStructure.keySet().iterator();
    while (e.hasNext()) {
      JavaProjectionAnnotation newAnnotation = (JavaProjectionAnnotation) e.next();
      Position newPosition = (Position) newStructure.get(newAnnotation);

      IJavaElement element = newAnnotation.getElement();
      /*
       * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=130472 and
       * https://bugs.eclipse.org/bugs/show_bug.cgi?id=127445 In the presence of syntax
       * errors, anonymous types may have a source range offset of 0. When such a situation is
       * encountered, we ignore the proposed folding range: if no corresponding folding range
       * exists, it is silently ignored; if there *is* a matching folding range, we ignore the
       * position update and keep the old range, in order to keep the folding structure
       * stable.
       */
      boolean isMalformedAnonymousType = newPosition.getOffset() == 0 &&
              element.getElementType() == IJavaElement.TYPE && isInnerType((IType) element);
      List annotations = (List) oldStructure.get(element);
      if (annotations == null) {
        if (!isMalformedAnonymousType)
          additions.put(newAnnotation, newPosition);
      } else {
        Iterator x = annotations.iterator();
        boolean matched = false;
        while (x.hasNext()) {
          Tuple tuple = (Tuple) x.next();
          JavaProjectionAnnotation existingAnnotation = tuple.annotation;
          Position existingPosition = tuple.position;
          if (newAnnotation.isComment() == existingAnnotation.isComment()) {
            boolean updateCollapsedState = ctx.allowCollapsing() &&
                    existingAnnotation.isCollapsed() != newAnnotation.isCollapsed();
            if (!isMalformedAnonymousType && existingPosition != null &&
                    (!newPosition.equals(existingPosition) || updateCollapsedState)) {
              existingPosition.setOffset(newPosition.getOffset());
              existingPosition.setLength(newPosition.getLength());
              if (updateCollapsedState)
                if (newAnnotation.isCollapsed())
                  existingAnnotation.markCollapsed();
                else
                  existingAnnotation.markExpanded();
              updates.add(existingAnnotation);
            }
            matched = true;
            x.remove();
            break;
          }
        }
        if (!matched)
          additions.put(newAnnotation, newPosition);

        if (annotations.isEmpty())
          oldStructure.remove(element);
      }
    }

    e = oldStructure.values().iterator();
    while (e.hasNext()) {
      List list = (List) e.next();
      int size = list.size();
      for (int i = 0; i < size; i++)
        deletions.add(((Tuple) list.get(i)).annotation);
    }

    match(deletions, additions, updates, ctx);

    Annotation[] deletedArray = deletions.toArray(new Annotation[deletions.size()]);
    Annotation[] changedArray = updates.toArray(new Annotation[updates.size()]);
    ctx.getModel().modifyAnnotations(deletedArray, additions, changedArray);

    ctx.fScanner.setSource(null);
  }

  private void computeFoldingStructure(FoldingStructureComputationContext ctx) {
    IParent parent = (IParent) fInput;
    try {
      if (!(fInput instanceof ISourceReference))
        return;
      String source = ((ISourceReference) fInput).getSource();
      if (source == null)
        return;

      ctx.getScanner().setSource(source.toCharArray());
      computeFoldingStructure(parent.getChildren(), ctx);
    } catch (JavaModelException x) {}
  }

  private void computeFoldingStructure(IJavaElement[] elements,
          FoldingStructureComputationContext ctx) throws JavaModelException {
    computeFoldingStructure(elements, ctx, false);
  }

  private void computeFoldingStructure(IJavaElement[] elements,
          FoldingStructureComputationContext ctx, boolean isInAnonymousType)
          throws JavaModelException {
    for (int i = 0; i < elements.length; i++) {
      IJavaElement element = elements[i];

      if (isInAnonymousType) {
        if (isAnomymousType(element))
          computeFoldingStructure(element, ctx);
      } else
        computeFoldingStructure(element, ctx);

      if (element instanceof IParent)
        computeFoldingStructure(((IParent) element).getChildren(), ctx,
                isAnomymousType(element));
    }
  }

  private boolean isAnomymousType(IJavaElement element) throws JavaModelException {
    return element instanceof IType && ((IType) element).isAnonymous();
  }

  public static class JavaImportPosition extends Position implements IProjectionPosition {

    private static final int IMPORT_KEYWORD_LENGTH = "import".length();
    private final IImportContainer importContainer;

    public JavaImportPosition(IRegion projectionRegion, IImportContainer importContainer) {
      super(projectionRegion.getOffset(), projectionRegion.getLength());
      this.importContainer = importContainer;
    }

    public int computeCaptionOffset(IDocument document) throws BadLocationException {
      return IMPORT_KEYWORD_LENGTH;
    }

    public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException {
      try {
        if (SourceRange.isAvailable(importContainer.getSourceRange()) &&
                importContainer.getSource() != null) {

          Region hiddenRegion = new Region(getOffset() + IMPORT_KEYWORD_LENGTH,
                  getLength() - IMPORT_KEYWORD_LENGTH);
          return new IRegion[] { hiddenRegion };
        }
      } catch (Exception e) {}

      return null;
    }
  }

  /**
   * Computes the folding structure for a given {@link IJavaElement java element}. Computed
   * projection annotations are
   * {@link JavaStyleClosuresFoldingProvider.FoldingStructureComputationContext#addProjectionRange(JavaStyleClosuresFoldingProvider.JavaProjectionAnnotation, Position)
   * added} to the computation context.
   * <p>
   * Subclasses may extend or replace. The default implementation creates projection annotations
   * for the following elements:
   * <ul>
   * <li>true members (not for top-level types)</li>
   * <li>the javadoc comments of any member</li>
   * <li>header comments (javadoc or multi-line comments appearing before the first type's javadoc
   * or before the package or import declarations).</li>
   * </ul>
   * </p>
   * @param element the java element to compute the folding structure for
   * @param ctx the computation context
   */
  protected void computeFoldingStructure(IJavaElement element,
          FoldingStructureComputationContext ctx) {

    IMethod lambdaMethod = null;
/*    boolean importContainer = false;*/
    boolean collapse = false;
    boolean collapseCode = true;
    switch (element.getElementType()) {

    case IJavaElement.IMPORT_CONTAINER: {

      IImportContainer importContainer = (IImportContainer) element;
      IRegion projectionRegion = computeImportProjectionRanges(importContainer, ctx);

      if (projectionRegion != null) {
        JavaImportPosition importPosition = new JavaImportPosition(projectionRegion,
                importContainer);

        ctx.addProjectionRange(new JavaProjectionAnnotation(ctx.collapseImportContainer(),
                ctx, element, true), importPosition);
      }

      return;
    }
    case IJavaElement.TYPE:
      collapseCode = isInnerType((IType) element) && !isAnonymousEnum((IType) element);
      collapse = ctx.collapseInnerTypes() && collapseCode;

      lambdaMethod = findLambdaMethodIn(element);

      if (lambdaMethod != null) {
        // Let collapse initially by default
        collapse = ctx.initial;
      } else {
        try {
          if (((IType) element).isAnonymous())
            return;
        } catch (Exception e) {}
      }
      break;
    case IJavaElement.METHOD:
    case IJavaElement.FIELD:
    case IJavaElement.INITIALIZER:
      collapse = ctx.collapseMembers();
      collapseCode = false;
      break;
    default:
      return;
    }

    IRegion[] regions = computeProjectionRanges((ISourceReference) element, ctx);

    if (regions.length == 0)
      return;

    // comments
    for (int i = 0; i < regions.length - 1; i++) {
      IRegion normalized = alignRegion(regions[i], ctx);
      if (normalized != null) {
        Position position = createCommentPosition(normalized);
        if (position != null) {
          boolean commentCollapse;
          if (i == 0 && (regions.length > 2 || ctx.hasHeaderComment()) &&
                  element == ctx.getFirstType()) {
            commentCollapse = ctx.collapseHeaderComments();
          } else {
            commentCollapse = ctx.collapseJavadoc();
          }
          ctx.addProjectionRange(new JavaProjectionAnnotation(commentCollapse, ctx,
                  element, true), position);
        }
      }
    }
    // code
    if (!collapseCode)
      return;

// IRegion lastRegion = regions[regions.length - 1];

    IRegion codeRegion = regions[regions.length - 1];

// if (lambdaMethod != null) {
// normalized = alignRegion(lastRegion, ctx);
// }

    if (codeRegion != null) {
      Position position = element instanceof IMember ? createMemberPosition(codeRegion,
              (IMember) element, lambdaMethod) : createCommentPosition(codeRegion);

      if (position != null)
        ctx.addProjectionRange(new JavaProjectionAnnotation(collapse, ctx, element, false),
                position);
    }
  }

  private IRegion computeImportProjectionRanges(IImportContainer element,
          FoldingStructureComputationContext ctx) {
    try {
      ISourceRange range = element.getSourceRange();
      String contents = null;
      if (SourceRange.isAvailable(range) && (contents = element.getSource()) != null) {
        Region importRegion = new Region(range.getOffset(), range.getLength());
        return importRegion;
        /*return alignRegion(importRegion, ctx);*/
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;

  }

  /**
   * Returns <code>true</code> if <code>type</code> is an anonymous enum declaration,
   * <code>false</code> otherwise. See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=143276
   * @param type the type to test
   * @return <code>true</code> if <code>type</code> is an anonymous enum declaration
   * @since 3.3
   */
  private boolean isAnonymousEnum(IType type) {
    try {
      return type.isEnum() && type.isAnonymous();
    } catch (JavaModelException x) {
      return false; // optimistically
    }
  }

  /**
   * Returns <code>true</code> if <code>type</code> is not a top-level type, <code>false</code> if
   * it is.
   * @param type the type to test
   * @return <code>true</code> if <code>type</code> is an inner type
   */
  private boolean isInnerType(IType type) {
    return type.getDeclaringType() != null;
  }

  /**
   * Computes the projection ranges for a given <code>ISourceReference</code>. More than one range
   * or none at all may be returned. If there are no foldable regions, an empty array is returned.
   * <p>
   * The last region in the returned array (if not empty) describes the region for the java
   * element that implements the source reference. Any preceding regions describe javadoc comments
   * of that java element.
   * </p>
   * @param reference a java element that is a source reference
   * @param ctx the folding context
   * @return the regions to be folded
   */
  protected final IRegion[] computeProjectionRanges(ISourceReference reference,
          FoldingStructureComputationContext ctx) {
    try {
      ISourceRange range = reference.getSourceRange();
      if (!SourceRange.isAvailable(range))
        return new IRegion[0];

      String contents = reference.getSource();
      if (contents == null)
        return new IRegion[0];

      List<IRegion> regions = new ArrayList<IRegion>();
      if (!ctx.hasFirstType() && reference instanceof IType) {
        ctx.setFirstType((IType) reference);
        IRegion headerComment = computeHeaderComment(ctx);
        if (headerComment != null) {
          regions.add(headerComment);
          ctx.setHasHeaderComment();
        }
      }

      final int shift = range.getOffset();
      IScanner scanner = ctx.getScanner();
      scanner.resetTo(shift, shift + range.getLength());

      int start = shift;
      while (true) {

        int token = scanner.getNextToken();
        start = scanner.getCurrentTokenStartPosition();

        switch (token) {
        case ITerminalSymbols.TokenNameCOMMENT_JAVADOC:
        case ITerminalSymbols.TokenNameCOMMENT_BLOCK: {
          int end = scanner.getCurrentTokenEndPosition() + 1;
          regions.add(new Region(start, end - start));
          continue;
        }
        case ITerminalSymbols.TokenNameCOMMENT_LINE:
          continue;
        }

        break;
      }

      regions.add(new Region(start, shift + range.getLength() - start));

      return regions.toArray(new IRegion[regions.size()]);
    } catch (JavaModelException e) {} catch (InvalidInputException e) {}

    return new IRegion[0];
  }

  private IRegion computeHeaderComment(FoldingStructureComputationContext ctx)
          throws JavaModelException {
    // search at most up to the first type
    ISourceRange range = ctx.getFirstType().getSourceRange();
    if (range == null)
      return null;
    int start = 0;
    int end = range.getOffset();

    /* code adapted from CommentFormattingStrategy:
     * scan the header content up to the first type. Once a comment is
     * found, accumulate any additional comments up to the stop condition.
     * The stop condition is reaching a package declaration, import container,
     * or the end of the input.
     */
    IScanner scanner = ctx.getScanner();
    scanner.resetTo(start, end);

    int headerStart = -1;
    int headerEnd = -1;
    try {
      boolean foundComment = false;
      int terminal = scanner.getNextToken();
      while (terminal != ITerminalSymbols.TokenNameEOF &&
              !(terminal == ITerminalSymbols.TokenNameclass ||
                      terminal == ITerminalSymbols.TokenNameinterface ||
                      terminal == ITerminalSymbols.TokenNameenum || (foundComment && (terminal == ITerminalSymbols.TokenNameimport || terminal == ITerminalSymbols.TokenNamepackage)))) {

        if (terminal == ITerminalSymbols.TokenNameCOMMENT_JAVADOC ||
                terminal == ITerminalSymbols.TokenNameCOMMENT_BLOCK ||
                terminal == ITerminalSymbols.TokenNameCOMMENT_LINE) {
          if (!foundComment)
            headerStart = scanner.getCurrentTokenStartPosition();
          headerEnd = scanner.getCurrentTokenEndPosition();
          foundComment = true;
        }
        terminal = scanner.getNextToken();
      }

    } catch (InvalidInputException ex) {
      return null;
    }

    if (headerEnd != -1) {
      return new Region(headerStart, headerEnd - headerStart);
    }
    return null;
  }

  /**
   * Creates a comment folding position from an
   * {@link #alignRegion(IRegion, JavaStyleClosuresFoldingProvider.FoldingStructureComputationContext)
   * aligned} region.
   * @param aligned an aligned region
   * @return a folding position corresponding to <code>aligned</code>
   */
  protected final Position createCommentPosition(IRegion aligned) {
    return new JavaCommentPosition(aligned.getOffset(), aligned.getLength());
  }

  /**
   * Creates a folding position that remembers its member from an
   * {@link #alignRegion(IRegion, JavaStyleClosuresFoldingProvider.FoldingStructureComputationContext)
   * aligned} region.
   * @param aligned an aligned region
   * @param member the member to remember
   * @return a folding position corresponding to <code>aligned</code>
   */
  protected final Position createMemberPosition(IRegion aligned, IMember member,
          IMethod lambdaMethod) {
    return new JavaElementPosition(aligned.getOffset(), aligned.getLength(), member,
            lambdaMethod);
  }

  /**
   * Aligns <code>region</code> to start and end at a line offset. The region's start is decreased
   * to the next line offset, and the end offset increased to the next line start or the end of
   * the document. <code>null</code> is returned if <code>region</code> is <code>null</code>
   * itself or does not comprise at least one line delimiter, as a single line cannot be folded.
   * @param region the region to align, may be <code>null</code>
   * @param ctx the folding context
   * @return a region equal or greater than <code>region</code> that is aligned with line offsets,
   *         <code>null</code> if the region is too small to be foldable (e.g. covers only one
   *         line)
   */
  protected final IRegion alignRegion(IRegion region, FoldingStructureComputationContext ctx) {
    if (region == null)
      return null;

    IDocument document = ctx.getDocument();

    try {

      int start = document.getLineOfOffset(region.getOffset());
      int end = document.getLineOfOffset(region.getOffset() + region.getLength());
      if (start >= end)
        return null;

      int offset = document.getLineOffset(start);
      int endOffset;
      if (document.getNumberOfLines() > end + 1)
        endOffset = document.getLineOffset(end + 1);
      else
        endOffset = document.getLineOffset(end) + document.getLineLength(end);

      return new Region(offset, endOffset - offset);

    } catch (BadLocationException x) {
      // concurrent modification
      return null;
    }
  }

  private ProjectionAnnotationModel getModel() {
    return (ProjectionAnnotationModel) fEditor.getAdapter(ProjectionAnnotationModel.class);
  }

  private IDocument getDocument() {
    JavaEditor editor = fEditor;
    if (editor == null)
      return null;

    IDocumentProvider provider = editor.getDocumentProvider();
    if (provider == null)
      return null;

    return provider.getDocument(editor.getEditorInput());
  }

  /**
   * Matches deleted annotations to changed or added ones. A deleted annotation/position tuple
   * that has a matching addition / change is updated and marked as changed. The matching tuple is
   * not added (for additions) or marked as deletion instead (for changes). The result is that
   * more annotations are changed and fewer get deleted/re-added.
   * @param deletions list with deleted annotations
   * @param additions map with position to annotation mappings
   * @param changes list with changed annotations
   * @param ctx the context
   */
  private void match(List<Annotation> deletions, Map<Annotation, Position> additions,
          List<Annotation> changes, FoldingStructureComputationContext ctx) {
    if (deletions.isEmpty() || (additions.isEmpty() && changes.isEmpty()))
      return;

    List<Annotation> newDeletions = new ArrayList<Annotation>();
    List<Annotation> newChanges = new ArrayList<Annotation>();

    Iterator deletionIterator = deletions.iterator();
    while (deletionIterator.hasNext()) {
      JavaProjectionAnnotation deleted = (JavaProjectionAnnotation) deletionIterator.next();
      Position deletedPosition = ctx.getModel().getPosition(deleted);
      if (deletedPosition == null)
        continue;

      Tuple deletedTuple = new Tuple(deleted, deletedPosition);

      Tuple match = findMatch(deletedTuple, changes, null, ctx);
      boolean addToDeletions = true;
      if (match == null) {
        match = findMatch(deletedTuple, additions.keySet(), additions, ctx);
        addToDeletions = false;
      }

      if (match != null) {
        IJavaElement element = match.annotation.getElement();
        deleted.setElement(element);
        deletedPosition.setLength(match.position.getLength());
        if (deletedPosition instanceof JavaElementPosition && element instanceof IMember) {
          JavaElementPosition jep = (JavaElementPosition) deletedPosition;
          jep.setMember((IMember) element);
        }

        deletionIterator.remove();
        newChanges.add(deleted);

        if (addToDeletions)
          newDeletions.add(match.annotation);
      }
    }

    deletions.addAll(newDeletions);
    changes.addAll(newChanges);
  }

  /**
   * Finds a match for <code>tuple</code> in a collection of annotations. The positions for the
   * <code>JavaProjectionAnnotation</code> instances in <code>annotations</code> can be found in
   * the passed <code>positionMap</code> or <code>fCachedModel</code> if <code>positionMap</code>
   * is <code>null</code>.
   * <p>
   * A tuple is said to match another if their annotations have the same comment flag and their
   * position offsets are equal.
   * </p>
   * <p>
   * If a match is found, the annotation gets removed from <code>annotations</code>.
   * </p>
   * @param tuple the tuple for which we want to find a match
   * @param annotations collection of <code>JavaProjectionAnnotation</code>
   * @param positionMap a <code>Map&lt;Annotation, Position&gt;</code> or <code>null</code>
   * @param ctx the context
   * @return a matching tuple or <code>null</code> for no match
   */
  private Tuple findMatch(Tuple tuple, Collection annotations, Map positionMap,
          FoldingStructureComputationContext ctx) {
    Iterator it = annotations.iterator();
    while (it.hasNext()) {
      JavaProjectionAnnotation annotation = (JavaProjectionAnnotation) it.next();
      if (tuple.annotation.isComment() == annotation.isComment()) {

        Position position = positionMap == null ? ctx.getModel().getPosition(annotation)
                : (Position) positionMap.get(annotation);
        if (position == null)
          continue;

        if (tuple.position.getOffset() == position.getOffset()) {
          it.remove();
          return new Tuple(annotation, position);
        }
      }
    }

    return null;
  }

  private Map computeCurrentStructure(FoldingStructureComputationContext ctx) {
    Map<IJavaElement, List<Tuple>> map = new HashMap<IJavaElement, List<Tuple>>();
    ProjectionAnnotationModel model = ctx.getModel();
    Iterator e = model.getAnnotationIterator();
    while (e.hasNext()) {
      Object annotation = e.next();
      if (annotation instanceof JavaProjectionAnnotation) {
        JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;
        Position position = model.getPosition(java);
        Assert.isNotNull(position);
        List<Tuple> list = map.get(java.getElement());
        if (list == null) {
          list = new ArrayList<Tuple>(2);
          map.put(java.getElement(), list);
        }
        list.add(new Tuple(java, position));
      }
    }

    Comparator<Tuple> comparator = new Comparator<Tuple>() {
      public int compare(Tuple o1, Tuple o2) {
        return o1.position.getOffset() - o2.position.getOffset();
      }
    };
    for (List<Tuple> list : map.values())
      Collections.sort(list, comparator);

    return map;
  }

  /*
   * @see IJavaFoldingStructureProviderExtension#collapseMembers()
   * @since 3.2
   */
  public final void collapseMembers() {
    modifyFiltered(fMemberFilter, false);
  }

  /*
   * @see IJavaFoldingStructureProviderExtension#collapseComments()
   * @since 3.2
   */
  public final void collapseComments() {
    modifyFiltered(fCommentFilter, false);
  }

  /*
   * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#collapseElements(org.eclipse.jdt.core.IJavaElement[])
   */
  public final void collapseElements(IJavaElement[] elements) {
    Set<IJavaElement> set = new HashSet<IJavaElement>(Arrays.asList(elements));
    modifyFiltered(new JavaElementSetFilter(set, false), false);
  }

  /*
   * @see org.eclipse.jdt.ui.text.folding.IJavaFoldingStructureProviderExtension#expandElements(org.eclipse.jdt.core.IJavaElement[])
   */
  public final void expandElements(IJavaElement[] elements) {
    Set<IJavaElement> set = new HashSet<IJavaElement>(Arrays.asList(elements));
    modifyFiltered(new JavaElementSetFilter(set, true), true);
  }

  /**
   * Collapses or expands all annotations matched by the passed filter.
   * @param filter the filter to use to select which annotations to collapse
   * @param expand <code>true</code> to expand the matched annotations, <code>false</code> to
   *            collapse them
   */
  private void modifyFiltered(Filter filter, boolean expand) {
    if (!isInstalled())
      return;

    ProjectionAnnotationModel model = getModel();
    if (model == null)
      return;

    List<Annotation> modified = new ArrayList<Annotation>();
    Iterator iter = model.getAnnotationIterator();
    while (iter.hasNext()) {
      Object annotation = iter.next();
      if (annotation instanceof JavaProjectionAnnotation) {
        JavaProjectionAnnotation java = (JavaProjectionAnnotation) annotation;

        if (expand == java.isCollapsed() && filter.match(java)) {
          if (expand)
            java.markExpanded();
          else
            java.markCollapsed();
          modified.add(java);
        }

      }
    }

    model.modifyAnnotations(null, null, modified.toArray(new Annotation[modified.size()]));
  }
}
TOP

Related Classes of com.googlecode.lambda4jdt.JavaStyleClosuresFoldingProvider$JavaImportPosition

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.