/*
* 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.transform;
import static com.oracle.truffle.dsl.processor.java.ElementUtils.getQualifiedName;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.AbstractAnnotationValueVisitor7;
import javax.lang.model.util.ElementFilter;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
import com.oracle.truffle.dsl.processor.java.model.CodeElementScanner;
import com.oracle.truffle.dsl.processor.java.model.CodeExecutableElement;
import com.oracle.truffle.dsl.processor.java.model.CodeImport;
import com.oracle.truffle.dsl.processor.java.model.CodeTree;
import com.oracle.truffle.dsl.processor.java.model.CodeTreeKind;
import com.oracle.truffle.dsl.processor.java.model.CodeTypeElement;
import com.oracle.truffle.dsl.processor.java.model.CodeVariableElement;
public abstract class AbstractCodeWriter extends CodeElementScanner<Void, Void> {
private static final int MAX_LINE_LENGTH = Integer.MAX_VALUE; // line wrapping disabled
private static final int LINE_WRAP_INDENTS = 3;
private static final String IDENT_STRING = " ";
private static final String LN = "\n"; /* unix style */
protected Writer writer;
private int indent;
private boolean newLine;
private int lineLength;
private boolean lineWrapping = false;
private OrganizedImports imports;
protected abstract Writer createWriter(CodeTypeElement clazz) throws IOException;
@Override
public Void visitType(CodeTypeElement e, Void p) {
if (e.isTopLevelClass()) {
Writer w = null;
try {
imports = OrganizedImports.organize(e);
w = new TrimTrailingSpaceWriter(createWriter(e));
writer = w;
writeRootClass(e);
} catch (IOException ex) {
throw new RuntimeException(ex);
} finally {
if (w != null) {
try {
w.close();
} catch (Throwable e1) {
// see eclipse bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=361378
// TODO temporary suppress errors on close.
}
}
writer = null;
}
} else {
writeClassImpl(e);
}
return null;
}
private void writeRootClass(CodeTypeElement e) {
writeHeader();
if (e.getPackageName() != null && e.getPackageName().length() > 0) {
write("package ").write(e.getPackageName()).write(";").writeLn();
writeEmptyLn();
}
Set<CodeImport> generateImports = imports.generateImports();
List<CodeImport> typeImports = new ArrayList<>();
List<CodeImport> staticImports = new ArrayList<>();
for (CodeImport codeImport : generateImports) {
if (codeImport.isStaticImport()) {
staticImports.add(codeImport);
} else {
typeImports.add(codeImport);
}
}
Collections.sort(typeImports);
Collections.sort(staticImports);
for (CodeImport imp : staticImports) {
imp.accept(this, null);
writeLn();
}
if (!staticImports.isEmpty()) {
writeEmptyLn();
}
for (CodeImport imp : typeImports) {
imp.accept(this, null);
writeLn();
}
if (!typeImports.isEmpty()) {
writeEmptyLn();
}
writeClassImpl(e);
}
private String useImport(Element enclosedType, TypeMirror type) {
if (imports != null) {
return imports.createTypeReference(enclosedType, type);
} else {
return ElementUtils.getSimpleName(type);
}
}
private void writeClassImpl(CodeTypeElement e) {
for (AnnotationMirror annotation : e.getAnnotationMirrors()) {
visitAnnotation(e, annotation);
writeLn();
}
writeModifiers(e.getModifiers(), true);
if (e.getKind() == ElementKind.ENUM) {
write("enum ");
} else {
write("class ");
}
write(e.getSimpleName());
if (e.getSuperclass() != null && !getQualifiedName(e.getSuperclass()).equals("java.lang.Object")) {
write(" extends ").write(useImport(e, e.getSuperclass()));
}
if (e.getImplements().size() > 0) {
write(" implements ");
for (int i = 0; i < e.getImplements().size(); i++) {
write(useImport(e, e.getImplements().get(i)));
if (i < e.getImplements().size() - 1) {
write(", ");
}
}
}
write(" {").writeLn();
writeEmptyLn();
indent(1);
List<VariableElement> staticFields = getStaticFields(e);
List<VariableElement> instanceFields = getInstanceFields(e);
for (int i = 0; i < staticFields.size(); i++) {
VariableElement field = staticFields.get(i);
field.accept(this, null);
if (e.getKind() == ElementKind.ENUM && i < staticFields.size() - 1) {
write(",");
writeLn();
} else {
write(";");
writeLn();
}
}
if (staticFields.size() > 0) {
writeEmptyLn();
}
for (VariableElement field : instanceFields) {
field.accept(this, null);
write(";");
writeLn();
}
if (instanceFields.size() > 0) {
writeEmptyLn();
}
for (ExecutableElement method : ElementFilter.constructorsIn(e.getEnclosedElements())) {
method.accept(this, null);
}
for (ExecutableElement method : getInstanceMethods(e)) {
method.accept(this, null);
}
for (ExecutableElement method : getStaticMethods(e)) {
method.accept(this, null);
}
for (TypeElement clazz : e.getInnerClasses()) {
clazz.accept(this, null);
}
dedent(1);
write("}");
writeEmptyLn();
}
private static List<VariableElement> getStaticFields(CodeTypeElement clazz) {
List<VariableElement> staticFields = new ArrayList<>();
for (VariableElement field : clazz.getFields()) {
if (field.getModifiers().contains(Modifier.STATIC)) {
staticFields.add(field);
}
}
return staticFields;
}
private static List<VariableElement> getInstanceFields(CodeTypeElement clazz) {
List<VariableElement> instanceFields = new ArrayList<>();
for (VariableElement field : clazz.getFields()) {
if (!field.getModifiers().contains(Modifier.STATIC)) {
instanceFields.add(field);
}
}
return instanceFields;
}
private static List<ExecutableElement> getStaticMethods(CodeTypeElement clazz) {
List<ExecutableElement> staticMethods = new ArrayList<>();
for (ExecutableElement method : clazz.getMethods()) {
if (method.getModifiers().contains(Modifier.STATIC)) {
staticMethods.add(method);
}
}
return staticMethods;
}
private static List<ExecutableElement> getInstanceMethods(CodeTypeElement clazz) {
List<ExecutableElement> instanceMethods = new ArrayList<>();
for (ExecutableElement method : clazz.getMethods()) {
if (!method.getModifiers().contains(Modifier.STATIC)) {
instanceMethods.add(method);
}
}
return instanceMethods;
}
@Override
public Void visitVariable(VariableElement f, Void p) {
Element parent = f.getEnclosingElement();
for (AnnotationMirror annotation : f.getAnnotationMirrors()) {
visitAnnotation(f, annotation);
write(" ");
}
CodeTree init = null;
if (f instanceof CodeVariableElement) {
init = ((CodeVariableElement) f).getInit();
}
if (parent != null && parent.getKind() == ElementKind.ENUM && f.getModifiers().contains(Modifier.STATIC)) {
write(f.getSimpleName());
if (init != null) {
write("(");
visitTree(init, p, f);
write(")");
}
} else {
writeModifiers(f.getModifiers(), true);
boolean varArgs = false;
if (parent != null && parent.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) parent;
if (method.isVarArgs() && method.getParameters().indexOf(f) == method.getParameters().size() - 1) {
varArgs = true;
}
}
TypeMirror varType = f.asType();
if (varArgs) {
if (varType.getKind() == TypeKind.ARRAY) {
varType = ((ArrayType) varType).getComponentType();
}
write(useImport(f, varType));
write("...");
} else {
write(useImport(f, varType));
}
write(" ");
write(f.getSimpleName());
if (init != null) {
write(" = ");
visitTree(init, p, f);
}
}
return null;
}
private void visitAnnotation(Element enclosedElement, AnnotationMirror e) {
write("@").write(useImport(enclosedElement, e.getAnnotationType()));
if (!e.getElementValues().isEmpty()) {
write("(");
final ExecutableElement defaultElement = findExecutableElement(e.getAnnotationType(), "value");
Map<? extends ExecutableElement, ? extends AnnotationValue> values = e.getElementValues();
if (defaultElement != null && values.size() == 1 && values.get(defaultElement) != null) {
visitAnnotationValue(enclosedElement, values.get(defaultElement));
} else {
Set<? extends ExecutableElement> methodsSet = values.keySet();
List<ExecutableElement> methodsList = new ArrayList<>();
for (ExecutableElement method : methodsSet) {
if (values.get(method) == null) {
continue;
}
methodsList.add(method);
}
Collections.sort(methodsList, new Comparator<ExecutableElement>() {
@Override
public int compare(ExecutableElement o1, ExecutableElement o2) {
return o1.getSimpleName().toString().compareTo(o2.getSimpleName().toString());
}
});
for (int i = 0; i < methodsList.size(); i++) {
ExecutableElement method = methodsList.get(i);
AnnotationValue value = values.get(method);
write(method.getSimpleName().toString());
write(" = ");
visitAnnotationValue(enclosedElement, value);
if (i < methodsList.size() - 1) {
write(", ");
}
}
}
write(")");
}
}
private void visitAnnotationValue(Element enclosedElement, AnnotationValue e) {
e.accept(new AnnotationValueWriterVisitor(enclosedElement), null);
}
private class AnnotationValueWriterVisitor extends AbstractAnnotationValueVisitor7<Void, Void> {
private final Element enclosedElement;
AnnotationValueWriterVisitor(Element enclosedElement) {
this.enclosedElement = enclosedElement;
}
@Override
public Void visitBoolean(boolean b, Void p) {
write(Boolean.toString(b));
return null;
}
@Override
public Void visitByte(byte b, Void p) {
write(Byte.toString(b));
return null;
}
@Override
public Void visitChar(char c, Void p) {
write(Character.toString(c));
return null;
}
@Override
public Void visitDouble(double d, Void p) {
write(Double.toString(d));
return null;
}
@Override
public Void visitFloat(float f, Void p) {
write(Float.toString(f));
return null;
}
@Override
public Void visitInt(int i, Void p) {
write(Integer.toString(i));
return null;
}
@Override
public Void visitLong(long i, Void p) {
write(Long.toString(i));
return null;
}
@Override
public Void visitShort(short s, Void p) {
write(Short.toString(s));
return null;
}
@Override
public Void visitString(String s, Void p) {
write("\"");
write(s);
write("\"");
return null;
}
@Override
public Void visitType(TypeMirror t, Void p) {
write(useImport(enclosedElement, t));
write(".class");
return null;
}
@Override
public Void visitEnumConstant(VariableElement c, Void p) {
write(useImport(enclosedElement, c.asType()));
write(".");
write(c.getSimpleName().toString());
return null;
}
@Override
public Void visitAnnotation(AnnotationMirror a, Void p) {
AbstractCodeWriter.this.visitAnnotation(enclosedElement, a);
return null;
}
@Override
public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
write("{");
for (int i = 0; i < vals.size(); i++) {
AnnotationValue value = vals.get(i);
AbstractCodeWriter.this.visitAnnotationValue(enclosedElement, value);
if (i < vals.size() - 1) {
write(", ");
}
}
write("}");
return null;
}
}
private static ExecutableElement findExecutableElement(DeclaredType type, String name) {
List<? extends ExecutableElement> elements = ElementFilter.methodsIn(type.asElement().getEnclosedElements());
for (ExecutableElement executableElement : elements) {
if (executableElement.getSimpleName().toString().equals(name)) {
return executableElement;
}
}
return null;
}
@Override
public void visitImport(CodeImport e, Void p) {
write("import ");
if (e.isStaticImport()) {
write("static ");
}
write(e.getPackageName());
write(".");
write(e.getSymbolName());
write(";");
}
@Override
public Void visitExecutable(CodeExecutableElement e, Void p) {
for (AnnotationMirror annotation : e.getAnnotationMirrors()) {
visitAnnotation(e, annotation);
writeLn();
}
writeModifiers(e.getModifiers(), !e.getEnclosingClass().getModifiers().contains(Modifier.FINAL));
if (e.getReturnType() != null) {
write(useImport(e, e.getReturnType()));
write(" ");
}
write(e.getSimpleName());
write("(");
for (int i = 0; i < e.getParameters().size(); i++) {
VariableElement param = e.getParameters().get(i);
param.accept(this, p);
if (i < e.getParameters().size() - 1) {
write(", ");
}
}
write(")");
List<TypeMirror> throwables = e.getThrownTypes();
if (throwables.size() > 0) {
write(" throws ");
for (int i = 0; i < throwables.size(); i++) {
write(useImport(e, throwables.get(i)));
if (i < throwables.size() - 1) {
write(", ");
}
}
}
if (e.getModifiers().contains(Modifier.ABSTRACT)) {
writeLn(";");
} else if (e.getBodyTree() != null) {
writeLn(" {");
indent(1);
visitTree(e.getBodyTree(), p, e);
dedent(1);
writeLn("}");
} else if (e.getBody() != null) {
write(" {");
write(e.getBody());
writeLn("}");
} else {
writeLn(" {");
writeLn("}");
}
writeEmptyLn();
return null;
}
@Override
public void visitTree(CodeTree e, Void p, Element enclosingElement) {
CodeTreeKind kind = e.getCodeKind();
switch (kind) {
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) {
write(", ");
}
}
}
break;
case GROUP:
super.visitTree(e, p, enclosingElement);
break;
case INDENT:
indent(1);
super.visitTree(e, p, enclosingElement);
dedent(1);
break;
case NEW_LINE:
writeLn();
break;
case STRING:
if (e.getString() != null) {
write(e.getString());
} else {
write("null");
}
break;
case STATIC_FIELD_REFERENCE:
if (e.getString() != null) {
write(imports.createStaticFieldReference(enclosingElement, e.getType(), e.getString()));
} else {
write("null");
}
break;
case STATIC_METHOD_REFERENCE:
if (e.getString() != null) {
write(imports.createStaticMethodReference(enclosingElement, e.getType(), e.getString()));
} else {
write("null");
}
break;
case TYPE:
write(useImport(enclosingElement, e.getType()));
break;
default:
assert false;
return;
}
}
protected void writeHeader() {
// default implementation does nothing
}
private void writeModifiers(Set<Modifier> modifiers, boolean includeFinal) {
if (modifiers != null && !modifiers.isEmpty()) {
Modifier[] modArray = modifiers.toArray(new Modifier[modifiers.size()]);
Arrays.sort(modArray);
for (Modifier mod : modArray) {
if (mod == Modifier.FINAL && !includeFinal) {
continue;
}
write(mod.toString());
write(" ");
}
}
}
private void indent(int count) {
indent += count;
}
private void dedent(int count) {
indent -= count;
}
private void writeLn() {
writeLn("");
}
protected void writeLn(String text) {
write(text);
write(LN);
lineLength = 0;
newLine = true;
if (lineWrapping) {
dedent(LINE_WRAP_INDENTS);
lineWrapping = false;
}
lineWrapping = false;
}
private void writeEmptyLn() {
writeLn();
}
private AbstractCodeWriter write(Name name) {
return write(name.toString());
}
private AbstractCodeWriter write(String m) {
if (m.isEmpty()) {
return this;
}
try {
String s = m;
lineLength += s.length();
if (newLine && s != LN) {
writeIndent();
newLine = false;
}
if (lineLength > MAX_LINE_LENGTH) {
s = wrapLine(s);
}
writer.write(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
private String wrapLine(String m) throws IOException {
assert !m.isEmpty();
char firstCharacter = m.charAt(0);
char lastCharacter = m.charAt(m.length() - 1);
if (firstCharacter == '\"' && lastCharacter == '\"') {
// string line wrapping
String string = m.substring(1, m.length() - 1);
if (string.isEmpty()) {
return m;
}
// restore original line length
lineLength = lineLength - m.length();
int size = 0;
for (int i = 0; i < string.length(); i += size) {
if (i != 0) {
write("+ ");
}
int nextSize = MAX_LINE_LENGTH - lineLength - 2;
if (nextSize <= 0) {
writeLn();
nextSize = MAX_LINE_LENGTH - lineLength - 2;
}
int end = Math.min(i + nextSize, string.length());
// TODO(CH): fails in normal usage - output ok though
// assert lineLength + (end - i) + 2 < MAX_LINE_LENGTH;
write("\"");
write(string.substring(i, end));
write("\"");
size = nextSize;
}
return "";
} else if (!Character.isAlphabetic(firstCharacter) && firstCharacter != '+') {
return m;
}
if (!lineWrapping) {
indent(LINE_WRAP_INDENTS);
}
lineWrapping = true;
lineLength = 0;
write(LN);
writeIndent();
return m;
}
private void writeIndent() throws IOException {
lineLength += indentSize();
for (int i = 0; i < indent; i++) {
writer.write(IDENT_STRING);
}
}
private int indentSize() {
return IDENT_STRING.length() * indent;
}
private static class TrimTrailingSpaceWriter extends Writer {
private final Writer delegate;
private final StringBuilder buffer = new StringBuilder();
TrimTrailingSpaceWriter(Writer delegate) {
this.delegate = delegate;
}
@Override
public void close() throws IOException {
this.delegate.close();
}
@Override
public void flush() throws IOException {
this.delegate.flush();
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
buffer.append(cbuf, off, len);
int newLinePoint = buffer.indexOf(LN);
if (newLinePoint != -1) {
String lhs = trimTrailing(buffer.substring(0, newLinePoint));
delegate.write(lhs);
delegate.write(LN);
buffer.delete(0, newLinePoint + 1);
}
}
private static String trimTrailing(String s) {
int cut = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (Character.isWhitespace(s.charAt(i))) {
cut++;
} else {
break;
}
}
if (cut > 0) {
return s.substring(0, s.length() - cut);
}
return s;
}
}
}