blob: c0e247e63324f8a4733d1ccbfd92b6e1d6a5cf33 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.source.save;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.ModuleTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.SourcePositions;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.main.JavaCompiler;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.swing.text.BadLocationException;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.util.Log;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.editor.indent.spi.Context.Region;
import org.netbeans.modules.editor.indent.spi.ExtraLock;
import org.netbeans.modules.editor.indent.spi.IndentTask;
import org.netbeans.modules.java.source.NoJavacHelper;
import org.netbeans.modules.java.source.TreeUtilitiesAccessor;
import org.netbeans.modules.java.source.parsing.FileObjects;
import org.netbeans.modules.java.source.parsing.JavacParser;
import org.netbeans.modules.java.source.parsing.ParsingUtils;
import org.netbeans.modules.java.source.parsing.PrefetchableJavaFileObject;
import org.netbeans.modules.parsing.impl.Utilities;
import org.openide.filesystems.FileObject;
/**
*
* @author Dusan Balek
*/
public class Reindenter implements IndentTask {
private final Context context;
private CodeStyle cs;
private TokenSequence<JavaTokenId> ts;
private String text;
private CompilationUnitTree cut;
private Tree parsedTree;
private SourcePositions sp;
private Map<Integer, Integer> newIndents;
private int currentEmbeddingStartOffset;
private int currentEmbeddingLength;
private Reindenter(Context context) {
this.context = context;
}
@Override
public void reindent() throws BadLocationException {
ts = null;
currentEmbeddingLength = -1;
newIndents = new HashMap<Integer, Integer>();
cs = CodeStyle.getDefault(context.document());
for (Region region : context.indentRegions()) {
if (initRegionData(region)) {
HashSet<Integer> linesToAddStar = new HashSet<Integer>();
LinkedList<Integer> startOffsets = getStartOffsets(region);
for (ListIterator<Integer> it = startOffsets.listIterator(); it.hasNext();) {
int startOffset = it.next();
int endOffset;
if (it.hasNext()) {
endOffset = it.next() - 1;
it.previous();
} else {
endOffset = region.getEndOffset();
}
String blockCommentLine;
int delta = ts.move(startOffset);
if (((startOffset == 0 || delta > 0) && ts.moveNext() || ts.movePrevious())
&& (ts.token().id() != JavaTokenId.BLOCK_COMMENT || ts.embedded() == null)) {
if (cs.addLeadingStarInComment()
&& (ts.token().id() == JavaTokenId.BLOCK_COMMENT && cs.enableBlockCommentFormatting()
|| ts.token().id() == JavaTokenId.JAVADOC_COMMENT && cs.enableJavadocFormatting())) {
blockCommentLine = ts.token().text().toString();
if (delta > 0) {
int idx = blockCommentLine.indexOf('\n', delta); //NOI18N
blockCommentLine = (idx < 0 ? blockCommentLine.substring(delta) : blockCommentLine.substring(delta, idx)).trim();
int off = ts.offset() + delta - 1;
int prevLineStartOffset = context.lineStartOffset(off < 0 ? startOffset : off);
Integer prevLineIndent = newIndents.get(prevLineStartOffset);
newIndents.put(startOffset, (prevLineIndent != null ? prevLineIndent : context.lineIndent(prevLineStartOffset)) + (prevLineStartOffset > ts.offset() ? 0 : 1)); //NOI18N
} else {
int idx = blockCommentLine.lastIndexOf('\n'); //NOI18N
if (idx > 0) {
blockCommentLine = blockCommentLine.substring(idx).trim();
}
newIndents.put(startOffset, getNewIndent(startOffset, endOffset) + 1);
}
if (!blockCommentLine.startsWith("*")) { //NOI18N
linesToAddStar.add(startOffset);
}
} else {
if (delta == 0 && ts.moveNext() && ts.token().id() == JavaTokenId.LINE_COMMENT) {
newIndents.put(startOffset, 0);
} else {
newIndents.put(startOffset, getNewIndent(startOffset, endOffset));
}
}
}
}
while (!startOffsets.isEmpty()) {
int startOffset = startOffsets.removeLast();
Integer newIndent = newIndents.get(startOffset);
if (linesToAddStar.contains(startOffset)) {
context.modifyIndent(startOffset, 0);
context.document().insertString(startOffset, "* ", null); //NOI18N
}
if (newIndent != null) {
context.modifyIndent(startOffset, newIndent);
}
if (!startOffsets.isEmpty()) {
char c;
int len = 0;
while ((c = text.charAt(startOffset - 2 - len)) != '\n' && Character.isWhitespace(c)) { //NOI18N
len++;
}
if (len > 0) {
context.document().remove(startOffset - 1 - len, len);
}
}
}
}
}
}
@Override
public ExtraLock indentLock() {
return null;
}
private boolean initRegionData(final Region region) {
if (ts == null || (currentEmbeddingLength >= 0
&& !(currentEmbeddingStartOffset <= region.getStartOffset()
&& currentEmbeddingStartOffset + currentEmbeddingLength >= region.getEndOffset()))) {
ts = null;
currentEmbeddingStartOffset = 0;
currentEmbeddingLength = context.document().getLength();
TokenSequence<?> tseq = TokenHierarchy.get(context.document()).tokenSequence();
while(tseq != null && (region.getStartOffset() == 0 || tseq.moveNext())) {
tseq.move(region.getStartOffset());
if (!tseq.moveNext() && !tseq.movePrevious()) {
return false;
}
if (tseq.language() == JavaTokenId.language()) {
ts = (TokenSequence<JavaTokenId>)tseq;
tseq.moveStart();
tseq.moveNext();
currentEmbeddingStartOffset = tseq.offset();
tseq.moveEnd();
tseq.movePrevious();
currentEmbeddingLength = tseq.offset() - currentEmbeddingStartOffset;
break;
}
tseq = tseq.embeddedJoined();
}
if (ts == null) {
return false;
}
ClassLoader origCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(Reindenter.class.getClassLoader());
JavacTaskImpl javacTask = (JavacTaskImpl)JavacTool.create().getTask(null, null, new DiagnosticListener<JavaFileObject>() {
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
}
}, Collections.singletonList("-proc:none"), null, Collections.<JavaFileObject>emptySet()); //NOI18N
com.sun.tools.javac.util.Context ctx = javacTask.getContext();
JavaCompiler.instance(ctx).genEndPos = true;
text = context.document().getText(currentEmbeddingStartOffset, currentEmbeddingLength);
if (JavacParser.MIME_TYPE.equals(context.mimePath())) {
FileObject fo = Utilities.getFileObject(context.document());
cut = ParsingUtils.parseArbitrarySource(javacTask, FileObjects.memoryFileObject("", fo != null ? fo.getNameExt() : "", text));
parsedTree = cut;
sp = JavacTrees.instance(ctx).getSourcePositions();
} else {
final SourcePositions[] psp = new SourcePositions[1];
cut = null;
parsedTree = TreeUtilitiesAccessor.getInstance().parseStatement(javacTask, "{" + text + "}", psp);
sp = new SourcePositions() {
@Override
public long getStartPosition(CompilationUnitTree file, Tree tree) {
return currentEmbeddingStartOffset + psp[0].getStartPosition(file, tree) - 1;
}
@Override
public long getEndPosition(CompilationUnitTree file, Tree tree) {
return currentEmbeddingStartOffset + psp[0].getEndPosition(file, tree) - 1;
}
};
}
} catch (Exception ex) {
return false;
} finally {
Thread.currentThread().setContextClassLoader(origCL);
}
}
return true;
}
private LinkedList<Integer> getStartOffsets(Region region) throws BadLocationException {
LinkedList<Integer> offsets = new LinkedList<Integer>();
int offset = region.getEndOffset();
int lso;
while (offset > 0 && (lso = context.lineStartOffset(offset)) >= region.getStartOffset()) {
offsets.addFirst(lso);
offset = lso - 1;
}
return offsets;
}
private int getNewIndent(int startOffset, int endOffset) throws BadLocationException {
LinkedList<? extends Tree> path = getPath(startOffset);
if (path.isEmpty()) {
return 0;
}
Tree last = path.getFirst();
int lastPos = getStartPosition(last);
int currentIndent = getCurrentIndent(last, path);
switch (last.getKind()) {
case COMPILATION_UNIT:
break;
case MODULE:
TokenSequence<JavaTokenId> token = findFirstNonWhitespaceToken(startOffset, endOffset);
JavaTokenId nextTokenId = token != null ? token.token().id() : null;
if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
if (isLeftBraceOnNewLine(lastPos, startOffset)) {
switch (cs.getModuleDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
}
} else {
Tree t = null;
for (Tree member : ((ModuleTree)last).getDirectives()) {
if (sp.getEndPosition(cut, member) > startOffset) {
break;
}
t = member;
}
if (t != null) {
int i = getCurrentIndent(t, path);
currentIndent = i < 0 ? currentIndent + (cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0) : i;
} else {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
JavaTokenId prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId != null) {
switch (prevTokenId) {
case LBRACE:
currentIndent += cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0;
break;
case IDENTIFIER:
if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getModuleDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else {
currentIndent += cs.getContinuationIndentSize();
}
break;
default:
currentIndent += cs.getContinuationIndentSize();
}
}
}
}
break;
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
if (isLeftBraceOnNewLine(lastPos, startOffset)) {
switch (cs.getClassDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
}
} else {
Tree t = null;
for (Tree member : ((ClassTree)last).getMembers()) {
if (getEndPosition(member) > startOffset) {
break;
}
t = member;
}
if (t != null) {
int i = getCurrentIndent(t, path);
currentIndent = i < 0 ? currentIndent + (cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0) : i;
} else {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
JavaTokenId prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId != null) {
switch (prevTokenId) {
case LBRACE:
if (path.size() > 1 && path.get(1).getKind() == Kind.NEW_CLASS && isLeftBraceOnNewLine(lastPos, startOffset)) {
switch (cs.getClassDeclBracePlacement()) {
case SAME_LINE:
case NEW_LINE:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() - cs.getIndentSize() / 2);
break;
}
} else {
currentIndent += cs.indentTopLevelClassMembers() ? cs.getIndentSize() : 0;
}
break;
case COMMA:
currentIndent = getMultilineIndent(((ClassTree)last).getImplementsClause(), path, token.offset(), currentIndent, cs.alignMultilineImplements(), true);
break;
case IDENTIFIER:
case GT:
case GTGT:
case GTGTGT:
if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getClassDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else {
currentIndent += cs.getContinuationIndentSize();
}
break;
default:
currentIndent += cs.getContinuationIndentSize();
}
}
}
}
break;
case METHOD:
token = findFirstNonWhitespaceToken(startOffset, lastPos);
JavaTokenId prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId != null) {
switch (prevTokenId) {
case COMMA:
List<? extends ExpressionTree> thrws = ((MethodTree)last).getThrows();
if (!thrws.isEmpty() && getStartPosition(thrws.get(0)) < token.offset()) {
currentIndent = getMultilineIndent(thrws, path, token.offset(), currentIndent, cs.alignMultilineThrows(), true);
} else {
currentIndent = getMultilineIndent(((MethodTree)last).getParameters(), path, token.offset(), currentIndent, cs.alignMultilineMethodParams(), true);
}
break;
case RPAREN:
case IDENTIFIER:
case GT:
case GTGT:
case GTGTGT:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token != null && token.token().id() == JavaTokenId.LBRACE) {
switch (cs.getMethodDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
break;
}
default:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token == null || token.token().id() != JavaTokenId.RPAREN) {
currentIndent += cs.getContinuationIndentSize();
}
}
}
break;
case VARIABLE:
Tree type = ((VariableTree)last).getType();
if (type != null && type.getKind() != Kind.ERRONEOUS) {
ExpressionTree init = ((VariableTree)last).getInitializer();
if (init == null || init.getKind() != Kind.NEW_ARRAY
|| (token = findFirstNonWhitespaceToken(startOffset, lastPos)) == null
|| token.token().id() != JavaTokenId.EQ
|| (token = findFirstNonWhitespaceToken(startOffset, endOffset)) == null
|| token.token().id() != JavaTokenId.LBRACE) {
if (cs.alignMultilineAssignment()) {
int c = getColumn(last);
if (c >= 0) {
currentIndent = c;
}
} else {
currentIndent += cs.getContinuationIndentSize();
}
} else {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
}
break;
} else {
last = ((VariableTree)last).getModifiers();
if (last == null)
break;
}
case MODIFIERS:
Tree t = null;
for (Tree ann : ((ModifiersTree)last).getAnnotations()) {
if (getEndPosition(ann) > startOffset) {
break;
}
t = ann;
}
if (t == null || findFirstNonWhitespaceToken(startOffset, getEndPosition(t)) != null) {
currentIndent += cs.getContinuationIndentSize();
}
break;
case DO_WHILE_LOOP:
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && !EnumSet.of(JavaTokenId.RBRACE, JavaTokenId.SEMICOLON).contains(token.token().id())) {
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.DO), lastPos, currentIndent);
}
break;
case ENHANCED_FOR_LOOP:
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((EnhancedForLoopTree)last).getExpression()), currentIndent);
break;
case FOR_LOOP:
LinkedList<Tree> forTrees = new LinkedList<Tree>();
for (StatementTree st : ((ForLoopTree)last).getInitializer()) {
if (getEndPosition(st) > startOffset) {
break;
}
forTrees.add(st);
}
t = ((ForLoopTree)last).getCondition();
if (t != null && getEndPosition(t) <= startOffset) {
forTrees.add(t);
}
for (ExpressionStatementTree est : ((ForLoopTree)last).getUpdate()) {
if (getEndPosition(est) > startOffset) {
break;
}
forTrees.add(est);
}
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() == JavaTokenId.SEMICOLON) {
currentIndent = getMultilineIndent(forTrees, path, token.offset(), currentIndent, cs.alignMultilineFor(), true);
} else {
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), forTrees.isEmpty() ? lastPos : getEndPosition(forTrees.getLast()), currentIndent);
}
break;
case IF:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token == null || token.token().id() != JavaTokenId.ELSE) {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && !EnumSet.of(JavaTokenId.RBRACE, JavaTokenId.SEMICOLON).contains(token.token().id())) {
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN, JavaTokenId.ELSE), getEndPosition(((IfTree)last).getCondition()) - 1, currentIndent);
}
}
break;
case SYNCHRONIZED:
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((SynchronizedTree)last).getExpression()) - 1, currentIndent);
break;
case TRY:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token == null || !EnumSet.of(JavaTokenId.CATCH, JavaTokenId.FINALLY).contains(token.token().id())) {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() != JavaTokenId.RBRACE) {
t = null;
for (Tree res : ((TryTree)last).getResources()) {
if (getEndPosition(res) > startOffset) {
break;
}
t = res;
}
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.TRY, JavaTokenId.RPAREN, JavaTokenId.FINALLY), t != null ? getEndPosition(t) : lastPos, currentIndent);
}
}
break;
case CATCH:
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), lastPos, currentIndent);
break;
case WHILE_LOOP:
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((WhileLoopTree)last).getCondition()) - 1, currentIndent);
break;
case BLOCK:
boolean isStatic = ((BlockTree)last).isStatic();
if (isStatic) {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() == JavaTokenId.STATIC && token.offset() == lastPos) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
break;
}
}
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
if (nextTokenId == null || nextTokenId != JavaTokenId.RBRACE) {
token = findFirstOtherToken(startOffset, lastPos + 1, EnumSet.of(JavaTokenId.WHITESPACE));
int prevTokenLineStartOffset = token != null ? context.lineStartOffset(token.offset()) : -1;
t = null;
boolean isNextLabeledStatement = false;
Iterator<? extends StatementTree> it = ((BlockTree)last).getStatements().iterator();
while (it.hasNext()) {
StatementTree st = it.next();
if (getEndPosition(st) > startOffset) {
isNextLabeledStatement = st.getKind() == Kind.LABELED_STATEMENT;
break;
}
t = st;
}
if (isNextLabeledStatement && cs.absoluteLabelIndent()) {
currentIndent = 0;
} else if (t != null) {
int i = -1;
if (getEndPosition(t) < prevTokenLineStartOffset) {
Integer newIndent = newIndents.get(prevTokenLineStartOffset);
i = newIndent != null ? newIndent : context.lineIndent(prevTokenLineStartOffset);
} else {
i = getCurrentIndent(t, path);
}
currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
} else if (isStatic) {
currentIndent += cs.getIndentSize();
} else if (isLeftBraceOnNewLine(lastPos, startOffset)) {
switch (path.size() > 1 && path.get(1).getKind() == Kind.METHOD ? cs.getMethodDeclBracePlacement() : cs.getOtherBracePlacement()) {
case SAME_LINE:
case NEW_LINE:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() - cs.getIndentSize() / 2);
break;
}
} else if (prevTokenLineStartOffset >= 0 && prevTokenLineStartOffset > context.lineStartOffset(lastPos)) {
Integer newIndent = newIndents.get(prevTokenLineStartOffset);
currentIndent = newIndent != null ? newIndent : context.lineIndent(prevTokenLineStartOffset);
} else {
int i = path.size() > 1 ? getCurrentIndent(path.get(1), path) : -1;
currentIndent = (i < 0 ? currentIndent : i) + cs.getIndentSize();
}
if (nextTokenId != null && nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
}
} else if (isStatic) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else if (!isLeftBraceOnNewLine(lastPos, startOffset)) {
int i = path.size() > 1 ? getCurrentIndent(path.get(1), path) : -1;
currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
}
break;
case SWITCH:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
if (nextTokenId != null && nextTokenId == JavaTokenId.RBRACE) {
if (isLeftBraceOnNewLine(lastPos, startOffset)) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
}
} else {
t = null;
for (CaseTree ct : ((SwitchTree)last).getCases()) {
if (getEndPosition(ct) > startOffset) {
break;
}
t = ct;
}
if (t != null) {
CaseTree ct = (CaseTree)t;
if (nextTokenId == null || !EnumSet.of(JavaTokenId.CASE, JavaTokenId.DEFAULT).contains(nextTokenId)) {
t = null;
for (StatementTree st : ct.getStatements()) {
if (getEndPosition(st) > startOffset) {
break;
}
t = st;
}
if (t != null) {
int i = getCurrentIndent(t, path);
currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(ct.getExpression()), currentIndent) : i;
} else {
int i = getCurrentIndent(ct, path);
currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(ct.getExpression()), currentIndent) : i;
currentIndent += cs.getIndentSize();
}
} else {
int i = getCurrentIndent(t, path);
currentIndent = i < 0 ? currentIndent + (cs.indentCasesFromSwitch() ? cs.getIndentSize() : 0) : i;
}
} else {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() == JavaTokenId.LBRACE) {
currentIndent += (cs.indentCasesFromSwitch() ? cs.getIndentSize() : 0);
} else {
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.RPAREN), getEndPosition(((SwitchTree)last).getExpression()) - 1, currentIndent);
}
}
}
break;
case CASE:
t = null;
for (StatementTree st : ((CaseTree)last).getStatements()) {
if (getEndPosition(st) > startOffset) {
break;
}
t = st;
}
if (t != null) {
int i = getCurrentIndent(t, path);
currentIndent = i < 0 ? getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(((CaseTree)last).getExpression()), currentIndent) : i;
} else {
currentIndent = getStmtIndent(startOffset, endOffset, EnumSet.of(JavaTokenId.COLON), getEndPosition(((CaseTree)last).getExpression()), currentIndent);
}
break;
case NEW_ARRAY:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
if (nextTokenId != JavaTokenId.RBRACE) {
token = findFirstNonWhitespaceToken(startOffset, lastPos);
prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId != null) {
switch (prevTokenId) {
case LBRACE:
currentIndent += cs.getIndentSize();
break;
case COMMA:
currentIndent = getMultilineIndent(((NewArrayTree)last).getInitializers(), path, token.offset(), currentIndent, cs.alignMultilineArrayInit(), false);
break;
case RBRACKET:
if (nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
break;
}
default:
currentIndent += cs.getContinuationIndentSize();
}
}
}
break;
case LAMBDA_EXPRESSION:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
token = findFirstNonWhitespaceToken(startOffset, lastPos);
prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId == JavaTokenId.ARROW && nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else if (nextTokenId != JavaTokenId.RPAREN) {
currentIndent = getContinuationIndent(path, currentIndent);
}
break;
case NEW_CLASS:
token = findFirstNonWhitespaceToken(startOffset, endOffset);
nextTokenId = token != null ? token.token().id() : null;
token = findFirstNonWhitespaceToken(startOffset, lastPos);
prevTokenId = token != null ? token.token().id() : null;
if (prevTokenId == JavaTokenId.RPAREN && nextTokenId == JavaTokenId.LBRACE) {
switch (cs.getClassDeclBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else if (nextTokenId != JavaTokenId.RPAREN) {
currentIndent = getContinuationIndent(path, currentIndent);
}
break;
case METHOD_INVOCATION:
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() == JavaTokenId.COMMA) {
currentIndent = getMultilineIndent(((MethodInvocationTree)last).getArguments(), path, token.offset(), currentIndent, cs.alignMultilineCallArgs(), true);
} else {
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token == null || token.token().id() != JavaTokenId.RPAREN) {
currentIndent = getContinuationIndent(path, currentIndent);
}
}
break;
case ANNOTATION:
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token != null && token.token().id() == JavaTokenId.COMMA) {
currentIndent = getMultilineIndent(((AnnotationTree)last).getArguments(), path, token.offset(), currentIndent, cs.alignMultilineAnnotationArgs(), true);
} else {
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token == null || token.token().id() != JavaTokenId.RPAREN) {
currentIndent = getContinuationIndent(path, currentIndent);
}
}
break;
case LABELED_STATEMENT:
token = findFirstNonWhitespaceToken(startOffset, lastPos);
if (token == null || token.token().id() != JavaTokenId.COLON) {
currentIndent = getContinuationIndent(path, currentIndent);
} else {
currentIndent += cs.getLabelIndent();
}
break;
default:
currentIndent = getContinuationIndent(path, currentIndent);
break;
}
return currentIndent;
}
private int getStartPosition(Tree last) {
return (int) sp.getStartPosition(cut, last);
}
private int getEndPosition(Tree last) {
return (int) sp.getEndPosition(cut, last);
}
private LinkedList<? extends Tree> getPath(final int startOffset) {
final LinkedList<Tree> path = new LinkedList<Tree>();
// When right at the token end move to previous token; otherwise move to the token that "contains" the offset
if (ts.move(startOffset) == 0 && startOffset > 0 || !ts.moveNext()) {
ts.movePrevious();
}
final int offset = (ts.token().id() == JavaTokenId.IDENTIFIER
|| ts.token().id().primaryCategory().startsWith("keyword") || //NOI18N
ts.token().id().primaryCategory().startsWith("string") || //NOI18N
ts.token().id().primaryCategory().equals("literal")) //NOI18N
? ts.offset() : startOffset;
new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void scan(Tree node, Void p) {
if (node != null) {
if (getStartPosition(node) < offset && getEndPosition(node) >= offset) {
super.scan(node, p);
if (node.getKind() != Tree.Kind.ERRONEOUS || !path.isEmpty()) {
path.add(node);
}
}
}
return null;
}
}.scan(parsedTree, null);
if (path.isEmpty() || path.getFirst() == parsedTree || getEndPosition(path.getFirst()) > offset) {
return path;
}
if (!path.isEmpty() && ts.move(offset) == 0) {
if (ts.movePrevious()) {
switch (ts.token().id()) {
case RPAREN:
if (!EnumSet.of(Kind.ENHANCED_FOR_LOOP, Kind.FOR_LOOP, Kind.IF, Kind.WHILE_LOOP, Kind.DO_WHILE_LOOP,
Kind.TYPE_CAST, Kind.SYNCHRONIZED).contains(path.getFirst().getKind())) {
path.removeFirst();
}
break;
case GTGTGT:
case GTGT:
case GT:
if (EnumSet.of(Kind.MEMBER_SELECT, Kind.CLASS, Kind.GREATER_THAN).contains(path.getFirst().getKind())) {
break;
}
case SEMICOLON:
if (path.getFirst().getKind() == Kind.FOR_LOOP
&& ts.offset() <= getStartPosition(((ForLoopTree)path.getFirst()).getUpdate().get(0))) {
break;
}
case RBRACE:
path.removeFirst();
switch (path.getFirst().getKind()) {
case CATCH:
path.removeFirst();
case METHOD:
case FOR_LOOP:
case ENHANCED_FOR_LOOP:
case IF:
case SYNCHRONIZED:
case WHILE_LOOP:
case TRY:
path.removeFirst();
}
break;
}
}
}
return path;
}
private TokenSequence<JavaTokenId> findFirstNonWhitespaceToken(int startOffset, int endOffset) {
return findFirstOtherToken(startOffset, endOffset, EnumSet.of(JavaTokenId.WHITESPACE, JavaTokenId.LINE_COMMENT, JavaTokenId.BLOCK_COMMENT, JavaTokenId.JAVADOC_COMMENT));
}
private TokenSequence<JavaTokenId> findFirstOtherToken(int startOffset, int endOffset, EnumSet<JavaTokenId> ids) {
if (startOffset == endOffset) {
return null;
}
ts.move(startOffset);
boolean backward = startOffset > endOffset;
while (backward ? ts.movePrevious() : ts.moveNext()) {
if (backward && ts.offset() < endOffset || !backward && ts.offset() > endOffset) {
return null;
}
if (!ids.contains(ts.token().id())) {
return ts;
}
}
return null;
}
private TokenSequence<JavaTokenId> findFirstTokenOccurrence(int startOffset, int endOffset, JavaTokenId token) {
if (startOffset == endOffset) {
return null;
}
ts.move(startOffset);
boolean backward = startOffset > endOffset;
while (backward ? ts.movePrevious() : ts.moveNext()) {
if (backward && ts.offset() < endOffset || !backward && ts.offset() > endOffset) {
return null;
}
if (ts.token().id() == token) {
return ts;
}
}
return null;
}
private boolean isLeftBraceOnNewLine(int startOffset, int endOffset) {
ts.move(startOffset);
while (ts.moveNext()) {
if (ts.offset() >= endOffset) {
return false;
}
if (ts.token().id() == JavaTokenId.LBRACE) {
if (!ts.movePrevious()) {
return false;
}
return ts.token().id() == JavaTokenId.LINE_COMMENT || ts.token().id() == JavaTokenId.WHITESPACE && ts.token().text().toString().indexOf('\n') >= 0;
}
}
return false;
}
private int getColumn(Tree tree) throws BadLocationException {
int startOffset = getStartPosition(tree);
if (startOffset < 0) {
return -1;
}
int lineStartOffset = context.lineStartOffset(startOffset);
return getCol(context.document().getText(lineStartOffset, startOffset - lineStartOffset));
}
private int getCurrentIndent(Tree tree, List<? extends Tree> path) throws BadLocationException {
int startOffset = -1;
switch (tree.getKind()) {
case METHOD_INVOCATION:
MethodInvocationTree mit = (MethodInvocationTree)tree;
startOffset = getEndPosition(mit.getMethodSelect());
TokenSequence<JavaTokenId> token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
if (token != null) {
startOffset = token.offset();
}
break;
case NEW_CLASS:
NewClassTree nct = (NewClassTree)tree;
startOffset = getEndPosition(nct.getIdentifier());
token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
if (token != null) {
startOffset = token.offset();
}
break;
case ANNOTATION:
AnnotationTree at = (AnnotationTree)tree;
startOffset = getEndPosition(at.getAnnotationType());
token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, getEndPosition(tree), JavaTokenId.LPAREN) : null;
if (token != null) {
startOffset = token.offset();
}
break;
case METHOD:
MethodTree mt = (MethodTree)tree;
startOffset = getEndPosition(mt.getReturnType());
if (startOffset < 0) {
startOffset = getEndPosition(mt.getModifiers());
}
if (startOffset < 0) {
startOffset = getStartPosition(tree);
}
token = startOffset >= 0 ? findFirstTokenOccurrence(startOffset, mt.getBody() != null ? getStartPosition(mt.getBody()) : getEndPosition(tree), JavaTokenId.LPAREN) : null;
if (token != null) {
startOffset = token.offset();
}
break;
}
if (startOffset < 0) {
startOffset = getStartPosition(tree);
}
if (startOffset < 0) {
startOffset = currentEmbeddingStartOffset;
}
int lineStartOffset = context.lineStartOffset(startOffset);
Integer newIndent = newIndents.get(lineStartOffset);
int currentIndent = newIndent != null ? newIndent : context.lineIndent(lineStartOffset);
if (cs.absoluteLabelIndent()) {
for (Iterator<? extends Tree> it = path.iterator(); it.hasNext();) {
Tree t = it.next();
if (t.getKind() == Tree.Kind.LABELED_STATEMENT && getStartPosition(t) == lineStartOffset) {
Tree parent = it.hasNext() ? it.next() : null;
if (parent != null && parent.getKind() == Kind.BLOCK) {
Tree stat = null;
for (StatementTree st : ((BlockTree)parent).getStatements()) {
if (getEndPosition(st) > startOffset) {
break;
}
stat = st;
}
if (stat != null) {
int i = getCurrentIndent(stat, path);
currentIndent = i < 0 ? currentIndent + cs.getIndentSize() : i;
} else {
int i = getCurrentIndent(parent, path);
currentIndent = (i < 0 ? currentIndent : i) + cs.getIndentSize();
}
}
break;
}
}
}
return currentIndent;
}
private int getContinuationIndent(LinkedList<? extends Tree> path, int currentIndent) throws BadLocationException {
for (Tree tree : path) {
switch (tree.getKind()) {
case CLASS:
case INTERFACE:
case ENUM:
case ANNOTATION_TYPE:
case VARIABLE:
case METHOD:
case TRY:
case RETURN:
case BLOCK:
case FOR_LOOP:
case SWITCH:
case THROW:
case WHILE_LOOP:
case IF:
case EXPRESSION_STATEMENT:
case SYNCHRONIZED:
case ASSERT:
case CONTINUE:
case LABELED_STATEMENT:
case ENHANCED_FOR_LOOP:
case BREAK:
case EMPTY_STATEMENT:
case DO_WHILE_LOOP:
int i = getCurrentIndent(tree, path);
return (i < 0 ? currentIndent : i) + cs.getContinuationIndentSize();
case METHOD_INVOCATION:
case NEW_CLASS:
case LAMBDA_EXPRESSION:
case ANNOTATION:
return currentIndent + cs.getContinuationIndentSize();
case ASSIGNMENT:
case MULTIPLY_ASSIGNMENT:
case DIVIDE_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
case PLUS_ASSIGNMENT:
case MINUS_ASSIGNMENT:
case LEFT_SHIFT_ASSIGNMENT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
case AND_ASSIGNMENT:
case XOR_ASSIGNMENT:
case OR_ASSIGNMENT:
if (cs.alignMultilineAssignment()) {
int c = getColumn(tree);
return c < 0 ? currentIndent : c;
}
break;
case AND:
case CONDITIONAL_AND:
case CONDITIONAL_OR:
case DIVIDE:
case EQUAL_TO:
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LEFT_SHIFT:
case LESS_THAN:
case LESS_THAN_EQUAL:
case MINUS:
case MULTIPLY:
case NOT_EQUAL_TO:
case OR:
case PLUS:
case REMAINDER:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
case XOR:
if (cs.alignMultilineBinaryOp()) {
int c = getColumn(tree);
return c < 0 ? currentIndent : c;
}
break;
case CONDITIONAL_EXPRESSION:
if (cs.alignMultilineTernaryOp()) {
int c = getColumn(tree);
return c < 0 ? currentIndent : c;
}
break;
}
}
return currentIndent + cs.getContinuationIndentSize();
}
private int getStmtIndent(int startOffset, int endOffset, Set<JavaTokenId> expectedTokenIds, int expectedTokenOffset, int currentIndent) {
TokenSequence<JavaTokenId> token = findFirstNonWhitespaceToken(startOffset, expectedTokenOffset);
if (token != null && expectedTokenIds.contains(token.token().id())) {
token = findFirstNonWhitespaceToken(startOffset, endOffset);
if (token != null && token.token().id() == JavaTokenId.LBRACE) {
switch (cs.getOtherBracePlacement()) {
case NEW_LINE_INDENTED:
currentIndent += cs.getIndentSize();
break;
case NEW_LINE_HALF_INDENTED:
currentIndent += (cs.getIndentSize() / 2);
break;
}
} else {
currentIndent += cs.getIndentSize();
}
} else {
currentIndent += cs.getContinuationIndentSize();
}
return currentIndent;
}
private int getMultilineIndent(List<? extends Tree> trees, LinkedList<? extends Tree> path, int commaOffset, int currentIndent, boolean align, boolean addContinuationIndent) throws BadLocationException {
Tree tree = null;
Tree first = null;
for (Tree t : trees) {
if (first == null) {
first = t;
}
if (getEndPosition(t) > commaOffset) {
break;
}
tree = t;
}
if (tree != null && findFirstNonWhitespaceToken(commaOffset, getEndPosition(tree)) == null) {
int firstStartOffset = getStartPosition(first);
int startOffset = first == tree ? firstStartOffset : getStartPosition(tree);
if (firstStartOffset < 0 || startOffset < 0) {
currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
} else {
int firstLineStartOffset = context.lineStartOffset(firstStartOffset);
int lineStartOffset = firstStartOffset == startOffset ? firstLineStartOffset : context.lineStartOffset(startOffset);
if (firstLineStartOffset != lineStartOffset) {
Integer newIndent = newIndents.get(lineStartOffset);
currentIndent = newIndent != null ? newIndent : context.lineIndent(lineStartOffset);
} else if (align) {
currentIndent = getCol(context.document().getText(lineStartOffset, startOffset - lineStartOffset));
} else {
currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
}
}
} else {
currentIndent = addContinuationIndent ? getContinuationIndent(path, currentIndent) : currentIndent + cs.getIndentSize();
}
return currentIndent;
}
private int getCol(String text) {
int col = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\t') {
col += cs.getTabSize();
col -= (col % cs.getTabSize());
} else {
col++;
}
}
return col;
}
public static class Factory implements IndentTask.Factory {
@Override
public IndentTask createTask(Context context) {
if (!NoJavacHelper.hasWorkingJavac())
return null;
return new Reindenter(context);
}
}
}