|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.github.javaparser.printer.lexicalpreservation; |
|
|
|
import static com.github.javaparser.GeneratedJavaParserConstants.*; |
|
import static com.github.javaparser.TokenTypes.eolTokenKind; |
|
import static com.github.javaparser.utils.Utils.assertNotNull; |
|
import static com.github.javaparser.utils.Utils.decapitalize; |
|
import static java.util.Comparator.comparing; |
|
import static java.util.stream.Collectors.toList; |
|
|
|
import java.io.IOException; |
|
import java.io.StringWriter; |
|
import java.io.Writer; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.lang.reflect.Method; |
|
import java.lang.reflect.ParameterizedType; |
|
import java.util.Collections; |
|
import java.util.IdentityHashMap; |
|
import java.util.Iterator; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Optional; |
|
|
|
import com.github.javaparser.JavaToken; |
|
import com.github.javaparser.Range; |
|
import com.github.javaparser.ast.DataKey; |
|
import com.github.javaparser.ast.Modifier; |
|
import com.github.javaparser.ast.Node; |
|
import com.github.javaparser.ast.NodeList; |
|
import com.github.javaparser.ast.body.VariableDeclarator; |
|
import com.github.javaparser.ast.comments.BlockComment; |
|
import com.github.javaparser.ast.comments.Comment; |
|
import com.github.javaparser.ast.comments.JavadocComment; |
|
import com.github.javaparser.ast.comments.LineComment; |
|
import com.github.javaparser.ast.nodeTypes.NodeWithVariables; |
|
import com.github.javaparser.ast.observer.AstObserver; |
|
import com.github.javaparser.ast.observer.ObservableProperty; |
|
import com.github.javaparser.ast.observer.PropagatingAstObserver; |
|
import com.github.javaparser.ast.type.PrimitiveType; |
|
import com.github.javaparser.ast.visitor.TreeVisitor; |
|
import com.github.javaparser.printer.ConcreteSyntaxModel; |
|
import com.github.javaparser.printer.concretesyntaxmodel.CsmElement; |
|
import com.github.javaparser.printer.concretesyntaxmodel.CsmIndent; |
|
import com.github.javaparser.printer.concretesyntaxmodel.CsmMix; |
|
import com.github.javaparser.printer.concretesyntaxmodel.CsmToken; |
|
import com.github.javaparser.printer.concretesyntaxmodel.CsmUnindent; |
|
import com.github.javaparser.utils.LineSeparator; |
|
import com.github.javaparser.utils.Pair; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class LexicalPreservingPrinter { |
|
|
|
private static String JAVA_UTIL_OPTIONAL = Optional.class.getCanonicalName(); |
|
private static String JAVAPARSER_AST_NODELIST = NodeList.class.getCanonicalName(); |
|
|
|
private static AstObserver observer; |
|
|
|
|
|
|
|
*/ |
|
public static final DataKey<NodeText> NODE_TEXT_DATA = new DataKey<NodeText>() { |
|
}; |
|
|
|
private static final LexicalDifferenceCalculator LEXICAL_DIFFERENCE_CALCULATOR = new LexicalDifferenceCalculator(); |
|
|
|
// |
|
// Factory methods |
|
// |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <N extends Node> N setup(N node) { |
|
assertNotNull(node); |
|
|
|
if (observer == null) { |
|
observer = createObserver(); |
|
} |
|
|
|
node.getTokenRange().ifPresent(r -> { |
|
storeInitialText(node); |
|
|
|
if (!node.isRegistered(observer)) { |
|
node.registerForSubtree(observer); |
|
} |
|
}); |
|
return node; |
|
} |
|
|
|
// |
|
// Constructor and setup |
|
// |
|
|
|
private static AstObserver createObserver() { |
|
return new LexicalPreservingPrinter.Observer(); |
|
} |
|
|
|
private static class Observer extends PropagatingAstObserver { |
|
|
|
@Override |
|
public void concretePropertyChange(Node observedNode, ObservableProperty property, Object oldValue, Object newValue) { |
|
if (oldValue == newValue) { |
|
|
|
return; |
|
} |
|
if (property == ObservableProperty.RANGE || property == ObservableProperty.COMMENTED_NODE) { |
|
return; |
|
} |
|
if (property == ObservableProperty.COMMENT) { |
|
Optional<Node> parentNode = observedNode.getParentNode(); |
|
NodeText nodeText = parentNode |
|
.map(parent -> getOrCreateNodeText(parentNode.get())) |
|
|
|
.orElse(getOrCreateNodeText(observedNode)); |
|
|
|
if (oldValue == null) { |
|
int index = parentNode.isPresent() ? |
|
|
|
nodeText.findChild(observedNode) : |
|
|
|
0; |
|
|
|
fixIndentOfMovedNode(nodeText, index); |
|
|
|
LineSeparator lineSeparator = observedNode.getLineEndingStyleOrDefault(LineSeparator.SYSTEM); |
|
nodeText.addElement(index, makeCommentToken((Comment) newValue)); |
|
nodeText.addToken(index + 1, eolTokenKind(lineSeparator), lineSeparator.asRawString()); |
|
} else if (newValue == null) { |
|
if (oldValue instanceof Comment) { |
|
if (((Comment) oldValue).isOrphan()) { |
|
nodeText = getOrCreateNodeText(observedNode); |
|
} |
|
int index = getIndexOfComment((Comment) oldValue, nodeText); |
|
nodeText.removeElement(index); |
|
if (nodeText.getElements().get(index).isNewline()) { |
|
nodeText.removeElement(index); |
|
} |
|
} else { |
|
throw new UnsupportedOperationException(); |
|
} |
|
} else { |
|
List<TokenTextElement> matchingTokens = findTokenTextElementForComment((Comment) oldValue, nodeText); |
|
|
|
if (matchingTokens.size() != 1) { |
|
throw new IllegalStateException("The matching comment to be replaced could not be found"); |
|
} |
|
|
|
Comment newComment = (Comment) newValue; |
|
TokenTextElement matchingElement = matchingTokens.get(0); |
|
nodeText.replace(matchingElement.and(matchingElement.matchByRange()), makeCommentToken(newComment)); |
|
} |
|
} |
|
NodeText nodeText = getOrCreateNodeText(observedNode); |
|
|
|
if (nodeText == null) { |
|
throw new NullPointerException(observedNode.getClass().getSimpleName()); |
|
} |
|
|
|
LEXICAL_DIFFERENCE_CALCULATOR.calculatePropertyChange(nodeText, observedNode, property, oldValue, newValue); |
|
} |
|
|
|
private TokenTextElement makeCommentToken(Comment newComment) { |
|
if (newComment.isJavadocComment()) { |
|
return new TokenTextElement(JAVADOC_COMMENT, "/**" + newComment.getContent() + "*/"); |
|
} |
|
if (newComment.isLineComment()) { |
|
return new TokenTextElement(SINGLE_LINE_COMMENT, "//" + newComment.getContent()); |
|
} |
|
if (newComment.isBlockComment()) { |
|
return new TokenTextElement(MULTI_LINE_COMMENT, "/*" + newComment.getContent() + "*/"); |
|
} |
|
throw new UnsupportedOperationException("Unknown type of comment: " + newComment.getClass().getSimpleName()); |
|
|
|
} |
|
|
|
private int getIndexOfComment(Comment oldValue, NodeText nodeText) { |
|
List<TokenTextElement> matchingTokens = findTokenTextElementForComment(oldValue, nodeText); |
|
|
|
if (!matchingTokens.isEmpty()) { |
|
TextElement matchingElement = matchingTokens.get(0); |
|
return nodeText.findElement(matchingElement.and(matchingElement.matchByRange())); |
|
} |
|
|
|
List<ChildTextElement> matchingChilds = findChildTextElementForComment(oldValue, nodeText); |
|
ChildTextElement matchingChild = matchingChilds.get(0); |
|
return nodeText.findElement(matchingChild.and(matchingChild.matchByRange())); |
|
} |
|
|
|
private List<ChildTextElement> findChildTextElementForComment(Comment oldValue, NodeText nodeText) { |
|
List<ChildTextElement> matchingChildElements; |
|
|
|
matchingChildElements = nodeText.getElements().stream() |
|
.filter(e -> e.isChild()) |
|
.map(c -> (ChildTextElement) c) |
|
.filter(c -> c.isComment()) |
|
.filter(c -> ((Comment) c.getChild()).getContent().equals(oldValue.getContent())) |
|
.collect(toList()); |
|
|
|
if (matchingChildElements.size() > 1) { |
|
|
|
matchingChildElements = matchingChildElements.stream() |
|
.filter(t -> isEqualRange(t.getChild().getRange(), oldValue.getRange())) |
|
.collect(toList()); |
|
} |
|
|
|
if (matchingChildElements.size() != 1) { |
|
throw new IllegalStateException("The matching child text element for the comment to be removed could not be found."); |
|
} |
|
|
|
return matchingChildElements; |
|
} |
|
|
|
private List<TokenTextElement> findTokenTextElementForComment(Comment oldValue, NodeText nodeText) { |
|
List<TokenTextElement> matchingTokens; |
|
|
|
if (oldValue instanceof JavadocComment) { |
|
matchingTokens = nodeText.getElements().stream() |
|
.filter(e -> e.isToken(JAVADOC_COMMENT)) |
|
.map(e -> (TokenTextElement) e) |
|
.filter(t -> t.getText().equals("/**" + oldValue.getContent() + "*/")) |
|
.collect(toList()); |
|
} else if (oldValue instanceof BlockComment) { |
|
matchingTokens = nodeText.getElements().stream() |
|
.filter(e -> e.isToken(MULTI_LINE_COMMENT)) |
|
.map(e -> (TokenTextElement) e) |
|
.filter(t -> t.getText().equals("/*" + oldValue.getContent() + "*/")) |
|
.collect(toList()); |
|
} else { |
|
matchingTokens = nodeText.getElements().stream() |
|
.filter(e -> e.isToken(SINGLE_LINE_COMMENT)) |
|
.map(e -> (TokenTextElement) e) |
|
.filter(t -> t.getText().trim().equals(("//" + oldValue.getContent()).trim())) |
|
.collect(toList()); |
|
} |
|
|
|
if (matchingTokens.size() > 1) { |
|
|
|
matchingTokens = matchingTokens.stream() |
|
.filter(t -> isEqualRange(t.getToken().getRange(), oldValue.getRange())) |
|
.collect(toList()); |
|
} |
|
|
|
return matchingTokens; |
|
} |
|
|
|
private boolean isEqualRange(Optional<Range> range1, Optional<Range> range2) { |
|
if (range1.isPresent() && range2.isPresent()) { |
|
return range1.get().equals(range2.get()); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void fixIndentOfMovedNode(NodeText nodeText, int index) { |
|
if (index <= 0) { |
|
return; |
|
} |
|
|
|
TextElement currentSpaceCandidate = null; |
|
|
|
for (int i = index - 1; i >= 0; i--) { |
|
TextElement spaceCandidate = nodeText.getTextElement(i); |
|
if (spaceCandidate.isSpaceOrTab()) { |
|
|
|
currentSpaceCandidate = nodeText.getTextElement(i); |
|
} |
|
if (!spaceCandidate.isSpaceOrTab()) { |
|
if (spaceCandidate.isNewline() && i != index - 1) { |
|
for (int j = 0; j < (index - 1) - i; j++) { |
|
if (currentSpaceCandidate != null) { |
|
|
|
nodeText.addElement(index, new TokenTextElement(JavaToken.Kind.SPACE.getKind(), currentSpaceCandidate.expand())); |
|
} else { |
|
|
|
nodeText.addElement(index, new TokenTextElement(JavaToken.Kind.SPACE.getKind())); |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public void concreteListChange(NodeList<?> changedList, ListChangeType type, int index, Node nodeAddedOrRemoved) { |
|
NodeText nodeText = getOrCreateNodeText(changedList.getParentNodeForChildren()); |
|
final List<DifferenceElement> differenceElements; |
|
if (type == AstObserver.ListChangeType.REMOVAL) { |
|
differenceElements = LEXICAL_DIFFERENCE_CALCULATOR.calculateListRemovalDifference(findNodeListName(changedList), changedList, index); |
|
} else if (type == AstObserver.ListChangeType.ADDITION) { |
|
differenceElements = LEXICAL_DIFFERENCE_CALCULATOR.calculateListAdditionDifference(findNodeListName(changedList), changedList, index, nodeAddedOrRemoved); |
|
} else { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
Difference difference = new Difference(differenceElements, nodeText, changedList.getParentNodeForChildren()); |
|
difference.apply(); |
|
} |
|
|
|
@Override |
|
public void concreteListReplacement(NodeList<?> changedList, int index, Node oldValue, Node newValue) { |
|
NodeText nodeText = getOrCreateNodeText(changedList.getParentNodeForChildren()); |
|
List<DifferenceElement> differenceElements = LEXICAL_DIFFERENCE_CALCULATOR.calculateListReplacementDifference(findNodeListName(changedList), changedList, index, newValue); |
|
|
|
Difference difference = new Difference(differenceElements, nodeText, changedList.getParentNodeForChildren()); |
|
difference.apply(); |
|
} |
|
} |
|
|
|
private static void storeInitialText(Node root) { |
|
Map<Node, List<JavaToken>> tokensByNode = new IdentityHashMap<>(); |
|
|
|
// We go over tokens and find to which nodes they belong. Note that we do not traverse the tokens as they were |
|
// on a list but as they were organized in a tree. At each time we select only the branch corresponding to the |
|
|
|
root.getTokenRange().ifPresent(rootTokenRange -> { |
|
for (JavaToken token : rootTokenRange) { |
|
Range tokenRange = token.getRange().orElseThrow(() -> new RuntimeException("Token without range: " + token)); |
|
Node owner = findNodeForToken(root, tokenRange).orElseThrow(() -> new RuntimeException("Token without node owning it: " + token)); |
|
if (!tokensByNode.containsKey(owner)) { |
|
tokensByNode.put(owner, new LinkedList<>()); |
|
} |
|
tokensByNode.get(owner).add(token); |
|
} |
|
|
|
|
|
new TreeVisitor() { |
|
@Override |
|
public void process(Node node) { |
|
if (!node.isPhantom()) { |
|
LexicalPreservingPrinter.storeInitialTextForOneNode(node, tokensByNode.get(node)); |
|
} |
|
} |
|
}.visitBreadthFirst(root); |
|
}); |
|
} |
|
|
|
private static Optional<Node> findNodeForToken(Node node, Range tokenRange) { |
|
if (node.isPhantom()) { |
|
return Optional.empty(); |
|
} |
|
if(!node.getRange().isPresent()) { |
|
return Optional.empty(); |
|
} |
|
if (!node.getRange().get().contains(tokenRange)) { |
|
return Optional.empty(); |
|
} |
|
|
|
for (Node child : node.getChildNodes()) { |
|
Optional<Node> found = findNodeForToken(child, tokenRange); |
|
if (found.isPresent()) { |
|
return found; |
|
} |
|
} |
|
return Optional.of(node); |
|
} |
|
|
|
private static void storeInitialTextForOneNode(Node node, List<JavaToken> nodeTokens) { |
|
if (nodeTokens == null) { |
|
nodeTokens = Collections.emptyList(); |
|
} |
|
List<Pair<Range, TextElement>> elements = new LinkedList<>(); |
|
for (Node child : node.getChildNodes()) { |
|
if (!child.isPhantom()) { |
|
if (!child.getRange().isPresent()) { |
|
throw new RuntimeException("Range not present on node " + child); |
|
} |
|
elements.add(new Pair<>(child.getRange().get(), new ChildTextElement(child))); |
|
} |
|
} |
|
for (JavaToken token : nodeTokens) { |
|
elements.add(new Pair<>(token.getRange().get(), new TokenTextElement(token))); |
|
} |
|
elements.sort(comparing(e -> e.a.begin)); |
|
node.setData(NODE_TEXT_DATA, new NodeText(elements.stream().map(p -> p.b).collect(toList()))); |
|
} |
|
|
|
// |
|
// Iterators |
|
// |
|
|
|
private static Iterator<TokenTextElement> tokensPreceeding(final Node node) { |
|
if (!node.getParentNode().isPresent()) { |
|
return new TextElementIteratorsFactory.EmptyIterator<>(); |
|
} |
|
// There is the awfully painful case of the fake types involved in variable declarators and |
|
// fields or variable declaration that are, of course, an exception... |
|
|
|
NodeText parentNodeText = getOrCreateNodeText(node.getParentNode().get()); |
|
int index = parentNodeText.tryToFindChild(node); |
|
if (index == NodeText.NOT_FOUND) { |
|
if (node.getParentNode().get() instanceof VariableDeclarator) { |
|
return tokensPreceeding(node.getParentNode().get()); |
|
} else { |
|
throw new IllegalArgumentException( |
|
String.format("I could not find child '%s' in parent '%s'. parentNodeText: %s", |
|
node, node.getParentNode().get(), parentNodeText)); |
|
} |
|
} |
|
|
|
return new TextElementIteratorsFactory.CascadingIterator<>( |
|
TextElementIteratorsFactory.partialReverseIterator(parentNodeText, index - 1), |
|
() -> tokensPreceeding(node.getParentNode().get())); |
|
} |
|
|
|
// |
|
// Printing methods |
|
// |
|
|
|
|
|
|
|
*/ |
|
public static String print(Node node) { |
|
StringWriter writer = new StringWriter(); |
|
try { |
|
print(node, writer); |
|
} catch (IOException e) { |
|
throw new RuntimeException("Unexpected IOException on a StringWriter", e); |
|
} |
|
return writer.toString(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static void print(Node node, Writer writer) throws IOException { |
|
if (!node.containsData(NODE_TEXT_DATA)) { |
|
getOrCreateNodeText(node); |
|
} |
|
final NodeText text = node.getData(NODE_TEXT_DATA); |
|
writer.append(text.expand()); |
|
} |
|
|
|
// |
|
// Methods to handle transformations |
|
// |
|
|
|
private static void prettyPrintingTextNode(Node node, NodeText nodeText) { |
|
if (node instanceof PrimitiveType) { |
|
PrimitiveType primitiveType = (PrimitiveType) node; |
|
switch (primitiveType.getType()) { |
|
case BOOLEAN: |
|
nodeText.addToken(BOOLEAN, node.toString()); |
|
break; |
|
case CHAR: |
|
nodeText.addToken(CHAR, node.toString()); |
|
break; |
|
case BYTE: |
|
nodeText.addToken(BYTE, node.toString()); |
|
break; |
|
case SHORT: |
|
nodeText.addToken(SHORT, node.toString()); |
|
break; |
|
case INT: |
|
nodeText.addToken(INT, node.toString()); |
|
break; |
|
case LONG: |
|
nodeText.addToken(LONG, node.toString()); |
|
break; |
|
case FLOAT: |
|
nodeText.addToken(FLOAT, node.toString()); |
|
break; |
|
case DOUBLE: |
|
nodeText.addToken(DOUBLE, node.toString()); |
|
break; |
|
default: |
|
throw new IllegalArgumentException(); |
|
} |
|
return; |
|
} |
|
if (node instanceof JavadocComment) { |
|
nodeText.addToken(JAVADOC_COMMENT, "/**" + ((JavadocComment) node).getContent() + "*/"); |
|
return; |
|
} |
|
if (node instanceof BlockComment) { |
|
nodeText.addToken(MULTI_LINE_COMMENT, "/*" + ((BlockComment) node).getContent() + "*/"); |
|
return; |
|
} |
|
if (node instanceof LineComment) { |
|
nodeText.addToken(SINGLE_LINE_COMMENT, "//" + ((LineComment) node).getContent()); |
|
return; |
|
} |
|
if (node instanceof Modifier) { |
|
Modifier modifier = (Modifier) node; |
|
nodeText.addToken(LexicalDifferenceCalculator.toToken(modifier), modifier.getKeyword().asString()); |
|
return; |
|
} |
|
|
|
interpret(node, ConcreteSyntaxModel.forClass(node.getClass()), nodeText); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static NodeText interpret(Node node, CsmElement csm, NodeText nodeText) { |
|
LexicalDifferenceCalculator.CalculatedSyntaxModel calculatedSyntaxModel = new LexicalDifferenceCalculator().calculatedSyntaxModelForNode(csm, node); |
|
|
|
List<TokenTextElement> indentation = findIndentation(node); |
|
|
|
boolean pendingIndentation = false; |
|
for (CsmElement element : calculatedSyntaxModel.elements) { |
|
if (element instanceof CsmIndent) { |
|
int indexCurrentElement = calculatedSyntaxModel.elements.indexOf(element); |
|
if (calculatedSyntaxModel.elements.size() > indexCurrentElement && |
|
!(calculatedSyntaxModel.elements.get(indexCurrentElement + 1) instanceof CsmUnindent)) { |
|
for (int i = 0; i < Difference.STANDARD_INDENTATION_SIZE; i++) { |
|
indentation.add(new TokenTextElement(SPACE, " ")); |
|
} |
|
} |
|
} else if (element instanceof CsmUnindent) { |
|
for (int i = 0; i < Difference.STANDARD_INDENTATION_SIZE && indentation.size() > 0; i++) { |
|
indentation.remove(indentation.size() - 1); |
|
} |
|
} |
|
|
|
if (pendingIndentation && !(element instanceof CsmToken && ((CsmToken) element).isNewLine())) { |
|
indentation.forEach(nodeText::addElement); |
|
} |
|
|
|
pendingIndentation = false; |
|
if (element instanceof LexicalDifferenceCalculator.CsmChild) { |
|
nodeText.addChild(((LexicalDifferenceCalculator.CsmChild) element).getChild()); |
|
} else if (element instanceof CsmToken) { |
|
CsmToken csmToken = (CsmToken) element; |
|
nodeText.addToken(csmToken.getTokenType(), csmToken.getContent(node)); |
|
if (csmToken.isNewLine()) { |
|
pendingIndentation = true; |
|
} |
|
} else if (element instanceof CsmMix) { |
|
CsmMix csmMix = (CsmMix) element; |
|
csmMix.getElements().forEach(e -> interpret(node, e, nodeText)); |
|
} else { |
|
// Indentation should probably be dealt with before because an indentation has effects also on the |
|
|
|
if (!(element instanceof CsmIndent) && !(element instanceof CsmUnindent)) { |
|
throw new UnsupportedOperationException(element.getClass().getSimpleName()); |
|
} |
|
} |
|
} |
|
// Array brackets are a pain... we do not have a way to represent them explicitly in the AST |
|
|
|
if (node instanceof VariableDeclarator) { |
|
VariableDeclarator variableDeclarator = (VariableDeclarator) node; |
|
variableDeclarator.getParentNode().ifPresent(parent -> |
|
((NodeWithVariables<?>) parent).getMaximumCommonType().ifPresent(mct -> { |
|
int extraArrayLevels = variableDeclarator.getType().getArrayLevel() - mct.getArrayLevel(); |
|
for (int i = 0; i < extraArrayLevels; i++) { |
|
nodeText.addElement(new TokenTextElement(LBRACKET)); |
|
nodeText.addElement(new TokenTextElement(RBRACKET)); |
|
} |
|
}) |
|
); |
|
} |
|
return nodeText; |
|
} |
|
|
|
|
|
static NodeText getOrCreateNodeText(Node node) { |
|
if (!node.containsData(NODE_TEXT_DATA)) { |
|
NodeText nodeText = new NodeText(); |
|
node.setData(NODE_TEXT_DATA, nodeText); |
|
prettyPrintingTextNode(node, nodeText); |
|
} |
|
return node.getData(NODE_TEXT_DATA); |
|
} |
|
|
|
|
|
static List<TokenTextElement> findIndentation(Node node) { |
|
List<TokenTextElement> followingNewlines = new LinkedList<>(); |
|
Iterator<TokenTextElement> it = tokensPreceeding(node); |
|
while (it.hasNext()) { |
|
TokenTextElement tte = it.next(); |
|
if (tte.getTokenKind() == SINGLE_LINE_COMMENT |
|
|| tte.isNewline()) { |
|
break; |
|
} else { |
|
followingNewlines.add(tte); |
|
} |
|
} |
|
Collections.reverse(followingNewlines); |
|
for (int i = 0; i < followingNewlines.size(); i++) { |
|
if (!followingNewlines.get(i).isSpaceOrTab()) { |
|
return followingNewlines.subList(0, i); |
|
} |
|
} |
|
return followingNewlines; |
|
} |
|
|
|
// |
|
// Helper methods |
|
// |
|
|
|
private static boolean isReturningOptionalNodeList(Method m) { |
|
if (!m.getReturnType().getCanonicalName().equals(JAVA_UTIL_OPTIONAL)) { |
|
return false; |
|
} |
|
if (!(m.getGenericReturnType() instanceof ParameterizedType)) { |
|
return false; |
|
} |
|
ParameterizedType parameterizedType = (ParameterizedType) m.getGenericReturnType(); |
|
java.lang.reflect.Type optionalArgument = parameterizedType.getActualTypeArguments()[0]; |
|
return (optionalArgument.getTypeName().startsWith(JAVAPARSER_AST_NODELIST)); |
|
} |
|
|
|
private static ObservableProperty findNodeListName(NodeList<?> nodeList) { |
|
Node parent = nodeList.getParentNodeForChildren(); |
|
for (Method m : parent.getClass().getMethods()) { |
|
if (m.getParameterCount() == 0 && m.getReturnType().getCanonicalName().equals(JAVAPARSER_AST_NODELIST)) { |
|
try { |
|
Object raw = m.invoke(parent); |
|
if (!(raw instanceof NodeList)) { |
|
throw new IllegalStateException("Expected NodeList, found " + raw.getClass().getCanonicalName()); |
|
} |
|
NodeList<?> result = (NodeList<?>) raw; |
|
if (result == nodeList) { |
|
String name = m.getName(); |
|
if (name.startsWith("get")) { |
|
name = name.substring("get".length()); |
|
} |
|
return ObservableProperty.fromCamelCaseName(decapitalize(name)); |
|
} |
|
} catch (IllegalAccessException | InvocationTargetException e) { |
|
throw new RuntimeException(e); |
|
} |
|
} else if (m.getParameterCount() == 0 && isReturningOptionalNodeList(m)) { |
|
try { |
|
Optional<NodeList<?>> raw = (Optional<NodeList<?>>) m.invoke(parent); |
|
if (raw.isPresent() && raw.get() == nodeList) { |
|
String name = m.getName(); |
|
if (name.startsWith("get")) { |
|
name = name.substring("get".length()); |
|
} |
|
return ObservableProperty.fromCamelCaseName(decapitalize(name)); |
|
} |
|
} catch (IllegalAccessException | InvocationTargetException e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
} |
|
throw new IllegalArgumentException("Cannot find list name of NodeList of size " + nodeList.size()); |
|
} |
|
} |