/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.dsl.processor.java.model;
import static com.oracle.truffle.dsl.processor.java.model.CodeTreeKind.GROUP;
import static com.oracle.truffle.dsl.processor.java.model.CodeTreeKind.NEW_LINE;
import static com.oracle.truffle.dsl.processor.java.model.CodeTreeKind.REMOVE_LAST;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
public class CodeTreeBuilder {
private BuilderCodeTree currentElement;
private final BuilderCodeTree root;
private int treeCount;
private Element enclosingElement;
public CodeTreeBuilder(CodeTreeBuilder parent) {
this.root = new BuilderCodeTree(null, GROUP, null, null);
this.currentElement = root;
if (parent != null) {
this.enclosingElement = parent.enclosingElement;
}
}
public void setEnclosingElement(Element enclosingElement) {
this.enclosingElement = enclosingElement;
}
@Override
public String toString() {
return root.toString();
}
public int getTreeCount() {
return treeCount;
}
public boolean isEmpty() {
return treeCount == 0;
}
public CodeTreeBuilder statement(String statement) {
return startStatement().string(statement).end();
}
public CodeTreeBuilder statement(CodeTree statement) {
return startStatement().tree(statement).end();
}
public static CodeTreeBuilder createBuilder() {
return new CodeTreeBuilder(null);
}
public static CodeTree singleString(String s) {
return createBuilder().string(s).build();
}
public static CodeTree singleType(TypeMirror s) {
return createBuilder().type(s).build();
}
private CodeTreeBuilder push(CodeTreeKind kind) {
return push(new BuilderCodeTree(currentElement, kind, null, null), kind == NEW_LINE);
}
private CodeTreeBuilder push(String string) {
return push(new BuilderCodeTree(currentElement, CodeTreeKind.STRING, null, string), false);
}
private CodeTreeBuilder push(TypeMirror type) {
return push(new BuilderCodeTree(currentElement, CodeTreeKind.TYPE, type, null), false);
}
private CodeTreeBuilder push(CodeTreeKind kind, TypeMirror type, String string) {
return push(new BuilderCodeTree(currentElement, kind, type, string), kind == NEW_LINE);
}
private CodeTreeBuilder push(BuilderCodeTree tree, boolean removeLast) {
if (currentElement != null) {
if (removeLast && !removeLastIfEnqueued(tree)) {
return this;
}
currentElement.add(tree);
}
switch (tree.getCodeKind()) {
case COMMA_GROUP:
case GROUP:
case INDENT:
currentElement = tree;
break;
}
treeCount++;
return this;
}
private boolean removeLastIfEnqueued(BuilderCodeTree tree) {
if (tree.getCodeKind() == REMOVE_LAST) {
return !clearLastRec(tree.removeLast, currentElement.getEnclosedElements());
}
List<CodeTree> childTree = tree.getEnclosedElements();
if (childTree != null && !childTree.isEmpty()) {
CodeTree last = childTree.get(0);
if (last instanceof BuilderCodeTree) {
if (!removeLastIfEnqueued((BuilderCodeTree) last)) {
childTree.remove(0);
}
}
}
return true;
}
private void clearLast(CodeTreeKind kind) {
if (clearLastRec(kind, currentElement.getEnclosedElements())) {
treeCount--;
} else {
// delay clearing the last
BuilderCodeTree tree = new BuilderCodeTree(currentElement, REMOVE_LAST, null, null);
tree.removeLast = kind;
push(tree, false);
}
}
public CodeTreeBuilder startStatement() {
startGroup();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
string(";").newLine();
}
@Override
public void afterEnd() {
}
});
return this;
}
public CodeTreeBuilder startGroup() {
return push(CodeTreeKind.GROUP);
}
public CodeTreeBuilder startCommaGroup() {
return push(CodeTreeKind.COMMA_GROUP);
}
public CodeTreeBuilder startCall(String callSite) {
return startCall((CodeTree) null, callSite);
}
public CodeTreeBuilder startCall(String receiver, String callSite) {
if (receiver != null) {
return startCall(singleString(receiver), callSite);
} else {
return startCall(callSite);
}
}
public CodeTreeBuilder startCall(CodeTree receiver, String callSite) {
if (receiver == null) {
return startGroup().string(callSite).startParanthesesCommaGroup().endAfter();
} else {
return startGroup().tree(receiver).string(".").string(callSite).startParanthesesCommaGroup().endAfter();
}
}
public CodeTreeBuilder startStaticCall(TypeMirror type, String methodName) {
return startGroup().push(CodeTreeKind.STATIC_METHOD_REFERENCE, type, methodName).startParanthesesCommaGroup().endAfter();
}
public CodeTreeBuilder startStaticCall(ExecutableElement method) {
return startStaticCall(ElementUtils.findNearestEnclosingType(method).asType(), method.getSimpleName().toString());
}
public CodeTreeBuilder staticReference(TypeMirror type, String fieldName) {
return push(CodeTreeKind.STATIC_FIELD_REFERENCE, type, fieldName);
}
private CodeTreeBuilder endAndWhitespaceAfter() {
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
string(" ");
end();
}
});
return this;
}
private CodeTreeBuilder endAfter() {
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
end();
}
});
return this;
}
private CodeTreeBuilder startParanthesesCommaGroup() {
startGroup();
string("(").startCommaGroup();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
string(")");
}
});
endAfter();
return this;
}
private CodeTreeBuilder startCurlyBracesCommaGroup() {
startGroup();
string("{").startCommaGroup();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
string("}");
}
});
endAfter();
return this;
}
public CodeTreeBuilder startParantheses() {
startGroup();
string("(").startGroup();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
string(")");
}
});
endAfter();
return this;
}
public CodeTreeBuilder doubleQuote(String s) {
return startGroup().string("\"" + s + "\"").end();
}
public CodeTreeBuilder string(String chunk1) {
return push(chunk1);
}
public CodeTreeBuilder string(String chunk1, String chunk2) {
return push(GROUP).string(chunk1).string(chunk2).end();
}
public CodeTreeBuilder string(String chunk1, String chunk2, String chunk3) {
return push(GROUP).string(chunk1).string(chunk2).string(chunk3).end();
}
public CodeTreeBuilder string(String chunk1, String chunk2, String chunk3, String chunk4) {
return push(GROUP).string(chunk1).string(chunk2).string(chunk3).string(chunk4).end();
}
public CodeTreeBuilder tree(CodeTree treeToAdd) {
if (treeToAdd instanceof BuilderCodeTree) {
return push((BuilderCodeTree) treeToAdd, true).end();
} else {
BuilderCodeTree tree = new BuilderCodeTree(currentElement, GROUP, null, null);
currentElement.add(treeToAdd);
return push(tree, true).end();
}
}
public CodeTreeBuilder trees(CodeTree... trees) {
for (CodeTree tree : trees) {
tree(tree);
}
return this;
}
public CodeTreeBuilder string(String chunk1, String chunk2, String chunk3, String chunk4, String... chunks) {
push(GROUP).string(chunk1).string(chunk2).string(chunk3).string(chunk4);
for (int i = 0; i < chunks.length; i++) {
string(chunks[i]);
}
return end();
}
public CodeTreeBuilder newLine() {
return push(NEW_LINE);
}
public CodeTreeBuilder startWhile() {
return startGroup().string("while ").startParanthesesCommaGroup().endAndWhitespaceAfter().startGroup().endAfter();
}
public CodeTreeBuilder startDoBlock() {
return startGroup().string("do ").startBlock();
}
public CodeTreeBuilder startDoWhile() {
clearLast(CodeTreeKind.NEW_LINE);
return startStatement().string(" while ").startParanthesesCommaGroup().endAfter().startGroup().endAfter();
}
public CodeTreeBuilder startIf() {
return startGroup().string("if ").startParanthesesCommaGroup().endAndWhitespaceAfter().startGroup().endAfter();
}
public CodeTreeBuilder startFor() {
return startGroup().string("for ").startParantheses().endAndWhitespaceAfter().startGroup().endAfter();
}
public boolean startIf(boolean elseIf) {
if (elseIf) {
startElseIf();
} else {
startIf();
}
return true;
}
public CodeTreeBuilder startElseIf() {
clearLast(CodeTreeKind.NEW_LINE);
return startGroup().string(" else if ").startParanthesesCommaGroup().endAndWhitespaceAfter().startGroup().endAfter();
}
public CodeTreeBuilder startElseBlock() {
clearLast(CodeTreeKind.NEW_LINE);
return startGroup().string(" else ").startBlock().endAfter();
}
private boolean clearLastRec(CodeTreeKind kind, List<CodeTree> children) {
if (children == null) {
return false;
}
for (int i = children.size() - 1; i >= 0; i--) {
CodeTree child = children.get(i);
if (child.getCodeKind() == kind) {
children.remove(children.get(i));
return true;
} else {
if (clearLastRec(kind, child.getEnclosedElements())) {
return true;
}
}
}
return false;
}
public CodeTreeBuilder startCase() {
startGroup().string("case ");
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
string(" :").newLine();
}
@Override
public void afterEnd() {
}
});
return this;
}
public CodeTreeBuilder caseDefault() {
return startGroup().string("default :").newLine().end();
}
public CodeTreeBuilder startSwitch() {
return startGroup().string("switch ").startParanthesesCommaGroup().endAndWhitespaceAfter();
}
public CodeTreeBuilder startReturn() {
ExecutableElement method = findMethod();
if (method != null && ElementUtils.isVoid(method.getReturnType())) {
startGroup();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
string(";").newLine(); // complete statement to execute
}
@Override
public void afterEnd() {
string("return").string(";").newLine(); // emit a return;
}
});
return this;
} else {
return startStatement().string("return ");
}
}
public CodeTreeBuilder startAssert() {
return startStatement().string("assert ");
}
public CodeTreeBuilder startNewArray(ArrayType arrayType, CodeTree size) {
startGroup().string("new ").type(arrayType.getComponentType()).string("[");
if (size != null) {
tree(size);
}
string("]");
if (size == null) {
string(" ");
startCurlyBracesCommaGroup().endAfter();
}
return this;
}
public CodeTreeBuilder lineComment(String text) {
return string("// ").string(text).newLine();
}
public CodeTreeBuilder startNew(TypeMirror uninializedNodeClass) {
return startGroup().string("new ").type(uninializedNodeClass).startParanthesesCommaGroup().endAfter();
}
public CodeTreeBuilder startNew(String typeName) {
return startGroup().string("new ").string(typeName).startParanthesesCommaGroup().endAfter();
}
public CodeTreeBuilder startIndention() {
return push(CodeTreeKind.INDENT);
}
public CodeTreeBuilder end(int times) {
for (int i = 0; i < times; i++) {
end();
}
return this;
}
public CodeTreeBuilder end() {
BuilderCodeTree tree = currentElement;
EndCallback callback = tree.getAtEndListener();
if (callback != null) {
callback.beforeEnd();
toParent();
callback.afterEnd();
} else {
toParent();
}
return this;
}
private void toParent() {
CodeTree parentElement = currentElement.getParent();
if (currentElement != root) {
this.currentElement = (BuilderCodeTree) parentElement;
} else {
this.currentElement = root;
}
}
public CodeTreeBuilder startBlock() {
startGroup();
string("{").newLine().startIndention();
registerCallBack(new EndCallback() {
@Override
public void beforeEnd() {
}
@Override
public void afterEnd() {
string("}").newLine();
}
});
endAfter();
return this;
}
public CodeTreeBuilder startSynchronized(String object) {
return startSynchronized(singleString(object));
}
public CodeTreeBuilder startSynchronized(CodeTree object) {
return string("synchronized").startParantheses().tree(object).end().startBlock();
}
private void registerCallBack(EndCallback callback) {
currentElement.registerAtEnd(callback);
}
public CodeTreeBuilder defaultDeclaration(TypeMirror type, String name) {
if (!ElementUtils.isVoid(type)) {
startStatement();
type(type);
string(" ");
string(name);
string(" = ");
defaultValue(type);
end(); // statement
}
return this;
}
public CodeTreeBuilder declaration(TypeMirror type, String name, String init) {
return declaration(type, name, singleString(init));
}
public CodeTreeBuilder declaration(String type, String name, CodeTree init) {
startStatement();
string(type);
string(" ");
string(name);
if (init != null) {
string(" = ");
tree(init);
}
end(); // statement
return this;
}
public CodeTreeBuilder declaration(String type, String name, String init) {
return declaration(type, name, singleString(init));
}
public CodeTreeBuilder declaration(TypeMirror type, String name, CodeTree init) {
if (ElementUtils.isVoid(type)) {
startStatement();
tree(init);
end();
} else {
startStatement();
type(type);
string(" ");
string(name);
if (init != null) {
string(" = ");
tree(init);
}
end(); // statement
}
return this;
}
public CodeTreeBuilder declaration(TypeMirror type, String name, CodeTreeBuilder init) {
if (init == this) {
throw new IllegalArgumentException("Recursive builder usage.");
}
return declaration(type, name, init.getTree());
}
public CodeTreeBuilder create() {
return new CodeTreeBuilder(this);
}
public CodeTreeBuilder type(TypeMirror type) {
return push(type);
}
public CodeTreeBuilder typeLiteral(TypeMirror type) {
return startGroup().type(ElementUtils.eraseGenericTypes(type)).string(".class").end();
}
private void assertRoot() {
if (currentElement != root) {
throw new IllegalStateException("CodeTreeBuilder was not ended properly.");
}
}
public CodeTreeBuilder startCaseBlock() {
return startIndention();
}
public CodeTreeBuilder startThrow() {
return startStatement().string("throw ");
}
public CodeTree getTree() {
assertRoot();
return root;
}
public CodeTree build() {
return root;
}
public CodeTreeBuilder cast(TypeMirror type) {
string("(").type(type).string(") ");
return this;
}
public CodeTreeBuilder cast(TypeMirror type, CodeTree content) {
if (ElementUtils.isVoid(type)) {
tree(content);
return this;
} else if (type.getKind() == TypeKind.DECLARED && ElementUtils.getQualifiedName(type).equals("java.lang.Object")) {
tree(content);
return this;
} else {
return startGroup().string("(").type(type).string(")").string(" ").tree(content).end();
}
}
public CodeTreeBuilder startSuperCall() {
return string("super").startParanthesesCommaGroup();
}
public CodeTreeBuilder returnFalse() {
return startReturn().string("false").end();
}
public CodeTreeBuilder returnStatement() {
return statement("return");
}
public ExecutableElement findMethod() {
if (enclosingElement != null && (enclosingElement.getKind() == ElementKind.METHOD || enclosingElement.getKind() == ElementKind.CONSTRUCTOR)) {
return (ExecutableElement) enclosingElement;
}
return null;
}
public CodeTreeBuilder returnNull() {
return startReturn().string("null").end();
}
public CodeTreeBuilder returnTrue() {
return startReturn().string("true").end();
}
public CodeTreeBuilder instanceOf(CodeTree var, TypeMirror type) {
return tree(var).string(" instanceof ").type(type);
}
public CodeTreeBuilder defaultValue(TypeMirror mirror) {
switch (mirror.getKind()) {
case VOID:
return string("");
case ARRAY:
case DECLARED:
case PACKAGE:
case NULL:
return string("null");
case BOOLEAN:
return string("false");
case BYTE:
return string("(byte) 0");
case CHAR:
return string("(char) 0");
case DOUBLE:
return string("0.0D");
case LONG:
return string("0L");
case INT:
return string("0");
case FLOAT:
return string("0.0F");
case SHORT:
return string("(short) 0");
default:
throw new AssertionError();
}
}
public CodeTreeBuilder startTryBlock() {
return string("try ").startBlock();
}
public CodeTreeBuilder startCatchBlock(TypeMirror exceptionType, String localVarName) {
clearLast(CodeTreeKind.NEW_LINE);
string(" catch (").type(exceptionType).string(" ").string(localVarName).string(") ");
return startBlock();
}
public CodeTreeBuilder startCatchBlock(TypeMirror[] exceptionTypes, String localVarName) {
clearLast(CodeTreeKind.NEW_LINE);
string(" catch (");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i != 0) {
string(" | ");
}
type(exceptionTypes[i]);
}
string(" ").string(localVarName).string(") ");
return startBlock();
}
public CodeTreeBuilder startFinallyBlock() {
clearLast(CodeTreeKind.NEW_LINE);
string(" finally ");
return startBlock();
}
public CodeTreeBuilder nullLiteral() {
return string("null");
}
private static class BuilderCodeTree extends CodeTree {
private EndCallback atEndListener;
private CodeTreeKind removeLast;
BuilderCodeTree(CodeTree parent, CodeTreeKind kind, TypeMirror type, String string) {
super(parent, kind, type, string);
}
public void registerAtEnd(EndCallback atEnd) {
if (this.atEndListener != null) {
this.atEndListener = new CompoundCallback(this.atEndListener, atEnd);
} else {
this.atEndListener = atEnd;
}
}
public EndCallback getAtEndListener() {
return atEndListener;
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
new Printer(b).visitTree(this, null, null);
return b.toString();
}
private static class CompoundCallback implements EndCallback {
private final EndCallback callback1;
private final EndCallback callback2;
CompoundCallback(EndCallback callback1, EndCallback callback2) {
this.callback1 = callback1;
this.callback2 = callback2;
}
@Override
public void afterEnd() {
callback1.afterEnd();
callback2.afterEnd();
}
@Override
public void beforeEnd() {
callback1.beforeEnd();
callback1.beforeEnd();
}
}
}
private interface EndCallback {
void beforeEnd();
void afterEnd();
}
private static class Printer extends CodeElementScanner<Void, Void> {
private int indent;
private boolean newLine;
private final String ln = "\n";
private final StringBuilder b;
Printer(StringBuilder b) {
this.b = b;
}
@Override
public void visitTree(CodeTree e, Void p, Element enclosingElement) {
switch (e.getCodeKind()) {
case COMMA_GROUP:
List<CodeTree> children = e.getEnclosedElements();
if (children != null) {
for (int i = 0; i < children.size(); i++) {
visitTree(children.get(i), p, enclosingElement);
if (i < e.getEnclosedElements().size() - 1) {
b.append(", ");
}
}
}
break;
case GROUP:
super.visitTree(e, p, enclosingElement);
break;
case INDENT:
indent();
super.visitTree(e, p, enclosingElement);
dedent();
break;
case NEW_LINE:
writeLn();
break;
case STRING:
if (e.getString() != null) {
write(e.getString());
} else {
write("null");
}
break;
case TYPE:
write(ElementUtils.getSimpleName(e.getType()));
break;
default:
assert false;
return;
}
}
private void indent() {
indent++;
}
private void dedent() {
indent--;
}
private void writeLn() {
write(ln);
newLine = true;
}
private void write(String m) {
if (newLine && m != ln) {
writeIndent();
newLine = false;
}
b.append(m);
}
private void writeIndent() {
for (int i = 0; i < indent; i++) {
b.append(" ");
}
}
}
public CodeTreeBuilder returnDefault() {
ExecutableElement method = findMethod();
if (ElementUtils.isVoid(method.getReturnType())) {
returnStatement();
} else {
startReturn().defaultValue(method.getReturnType()).end();
}
return this;
}
}