mcimadamore@1336: /* vromero@1482: * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. mcimadamore@1336: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. mcimadamore@1336: * mcimadamore@1336: * This code is free software; you can redistribute it and/or modify it mcimadamore@1336: * under the terms of the GNU General Public License version 2 only, as mcimadamore@1336: * published by the Free Software Foundation. mcimadamore@1336: * mcimadamore@1336: * This code is distributed in the hope that it will be useful, but WITHOUT mcimadamore@1336: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or mcimadamore@1336: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License mcimadamore@1336: * version 2 for more details (a copy is included in the LICENSE file that mcimadamore@1336: * accompanied this code). mcimadamore@1336: * mcimadamore@1336: * You should have received a copy of the GNU General Public License version mcimadamore@1336: * 2 along with this work; if not, write to the Free Software Foundation, mcimadamore@1336: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. mcimadamore@1336: * mcimadamore@1336: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA mcimadamore@1336: * or visit www.oracle.com if you need additional information or have any mcimadamore@1336: * questions. mcimadamore@1336: */ mcimadamore@1336: mcimadamore@1336: /* mcimadamore@1336: * @test mcimadamore@1336: * @bug 7194586 vromero@1520: * @bug 8003280 8006694 mcimadamore@1415: * @summary Add lambda tests mcimadamore@1415: * Add back-end support for invokedynamic vromero@1520: * temporarily workaround combo tests are causing time out in several platforms vromero@1482: * @library ../lib vromero@1482: * @build JavacTestingAbstractThreadedTest vromero@1520: * @run main/othervm TestInvokeDynamic mcimadamore@1336: */ mcimadamore@1336: vromero@1520: // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047) vromero@1520: // see JDK-8006746 vromero@1520: mcimadamore@1336: import com.sun.source.tree.MethodInvocationTree; mcimadamore@1336: import com.sun.source.tree.MethodTree; mcimadamore@1336: import com.sun.source.util.TaskEvent; mcimadamore@1336: import com.sun.source.util.TaskListener; mcimadamore@1336: import com.sun.source.util.TreeScanner; mcimadamore@1336: mcimadamore@1336: import com.sun.tools.classfile.Attribute; mcimadamore@1336: import com.sun.tools.classfile.BootstrapMethods_attribute; mcimadamore@1336: import com.sun.tools.classfile.ClassFile; mcimadamore@1336: import com.sun.tools.classfile.Code_attribute; mcimadamore@1336: import com.sun.tools.classfile.ConstantPool.*; mcimadamore@1336: import com.sun.tools.classfile.Instruction; mcimadamore@1336: import com.sun.tools.classfile.Method; mcimadamore@1336: mcimadamore@1336: import com.sun.tools.javac.api.JavacTaskImpl; mcimadamore@1336: import com.sun.tools.javac.code.Symbol; mcimadamore@1336: import com.sun.tools.javac.code.Symbol.MethodSymbol; mcimadamore@1336: import com.sun.tools.javac.code.Symtab; vromero@1452: import com.sun.tools.javac.code.Types; mcimadamore@1336: import com.sun.tools.javac.jvm.Pool; mcimadamore@1336: import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; mcimadamore@1336: import com.sun.tools.javac.tree.JCTree.JCMethodDecl; mcimadamore@1336: import com.sun.tools.javac.tree.JCTree.JCIdent; mcimadamore@1336: import com.sun.tools.javac.util.Context; mcimadamore@1336: import com.sun.tools.javac.util.Names; mcimadamore@1336: mcimadamore@1336: import java.io.File; mcimadamore@1336: import java.net.URI; mcimadamore@1336: import java.util.ArrayList; mcimadamore@1336: import java.util.Arrays; mcimadamore@1336: import java.util.Locale; mcimadamore@1336: mcimadamore@1336: import javax.tools.Diagnostic; mcimadamore@1336: import javax.tools.JavaFileObject; mcimadamore@1336: import javax.tools.SimpleJavaFileObject; mcimadamore@1336: mcimadamore@1336: import static com.sun.tools.javac.jvm.ClassFile.*; mcimadamore@1336: vromero@1482: public class TestInvokeDynamic vromero@1482: extends JavacTestingAbstractThreadedTest vromero@1482: implements Runnable { mcimadamore@1336: mcimadamore@1336: enum StaticArgumentKind { mcimadamore@1336: STRING("Hello!", "String", "Ljava/lang/String;") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_String_info) && vromero@1482: ((CONSTANT_String_info)cpInfo).getString() vromero@1482: .equals(value); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: CLASS(null, "Class", "Ljava/lang/Class;") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_Class_info) && vromero@1482: ((CONSTANT_Class_info)cpInfo).getName() vromero@1482: .equals("java/lang/String"); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: INTEGER(1, "int", "I") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_Integer_info) && vromero@1482: ((CONSTANT_Integer_info)cpInfo).value == vromero@1482: ((Integer)value).intValue(); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: LONG(1L, "long", "J") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_Long_info) && vromero@1482: ((CONSTANT_Long_info)cpInfo).value == vromero@1482: ((Long)value).longValue(); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: FLOAT(1.0f, "float", "F") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_Float_info) && vromero@1482: ((CONSTANT_Float_info)cpInfo).value == vromero@1482: ((Float)value).floatValue(); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: DOUBLE(1.0, "double","D") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_Double_info) && vromero@1482: ((CONSTANT_Double_info)cpInfo).value == vromero@1482: ((Double)value).doubleValue(); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { vromero@1482: if (!(cpInfo instanceof CONSTANT_MethodHandle_info)) vromero@1482: return false; vromero@1482: CONSTANT_MethodHandle_info handleInfo = vromero@1482: (CONSTANT_MethodHandle_info)cpInfo; mcimadamore@1336: return handleInfo.getCPRefInfo().getClassName().equals("Array") && mcimadamore@1336: handleInfo.reference_kind == RefKind.REF_invokeVirtual && vromero@1482: handleInfo.getCPRefInfo() vromero@1482: .getNameAndTypeInfo().getName().equals("clone") && vromero@1482: handleInfo.getCPRefInfo() vromero@1482: .getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;"); mcimadamore@1336: } mcimadamore@1336: }, mcimadamore@1336: METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") { mcimadamore@1336: @Override mcimadamore@1336: boolean check(CPInfo cpInfo) throws Exception { mcimadamore@1336: return (cpInfo instanceof CONSTANT_MethodType_info) && vromero@1482: ((CONSTANT_MethodType_info)cpInfo).getType() vromero@1482: .equals("()Ljava/lang/Object;"); mcimadamore@1336: } mcimadamore@1336: }; mcimadamore@1336: mcimadamore@1336: Object value; mcimadamore@1336: String sourceTypeStr; mcimadamore@1336: String bytecodeTypeStr; mcimadamore@1336: vromero@1482: StaticArgumentKind(Object value, String sourceTypeStr, vromero@1482: String bytecodeTypeStr) { mcimadamore@1336: this.value = value; mcimadamore@1336: this.sourceTypeStr = sourceTypeStr; mcimadamore@1336: this.bytecodeTypeStr = bytecodeTypeStr; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: abstract boolean check(CPInfo cpInfo) throws Exception; mcimadamore@1336: vromero@1452: Object getValue(Symtab syms, Names names, Types types) { mcimadamore@1336: switch (this) { mcimadamore@1336: case STRING: mcimadamore@1336: case INTEGER: mcimadamore@1336: case LONG: mcimadamore@1336: case FLOAT: mcimadamore@1336: case DOUBLE: mcimadamore@1336: return value; mcimadamore@1336: case CLASS: mcimadamore@1336: return syms.stringType.tsym; mcimadamore@1336: case METHOD_HANDLE: vromero@1482: return new Pool.MethodHandle(REF_invokeVirtual, vromero@1482: syms.arrayCloneMethod, types); mcimadamore@1336: case METHOD_TYPE: mcimadamore@1336: return syms.arrayCloneMethod.type; mcimadamore@1336: default: mcimadamore@1336: throw new AssertionError(); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: enum StaticArgumentsArity { mcimadamore@1336: ZERO(0), mcimadamore@1336: ONE(1), mcimadamore@1336: TWO(2), mcimadamore@1336: THREE(3); mcimadamore@1336: mcimadamore@1336: int arity; mcimadamore@1336: mcimadamore@1336: StaticArgumentsArity(int arity) { mcimadamore@1336: this.arity = arity; mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: public static void main(String... args) throws Exception { mcimadamore@1336: for (StaticArgumentsArity arity : StaticArgumentsArity.values()) { mcimadamore@1336: if (arity.arity == 0) { vromero@1482: pool.execute(new TestInvokeDynamic(arity)); mcimadamore@1336: } else { mcimadamore@1336: for (StaticArgumentKind sak1 : StaticArgumentKind.values()) { mcimadamore@1336: if (arity.arity == 1) { vromero@1482: pool.execute(new TestInvokeDynamic(arity, sak1)); mcimadamore@1336: } else { mcimadamore@1336: for (StaticArgumentKind sak2 : StaticArgumentKind.values()) { mcimadamore@1336: if (arity.arity == 2) { vromero@1482: pool.execute(new TestInvokeDynamic(arity, sak1, sak2)); mcimadamore@1336: } else { mcimadamore@1336: for (StaticArgumentKind sak3 : StaticArgumentKind.values()) { vromero@1482: pool.execute( vromero@1482: new TestInvokeDynamic(arity, sak1, sak2, sak3)); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: vromero@1482: checkAfterExec(); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: StaticArgumentsArity arity; mcimadamore@1336: StaticArgumentKind[] saks; mcimadamore@1336: DiagChecker dc; mcimadamore@1336: mcimadamore@1336: TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) { mcimadamore@1336: this.arity = arity; mcimadamore@1336: this.saks = saks; mcimadamore@1336: dc = new DiagChecker(); mcimadamore@1336: } mcimadamore@1336: vromero@1482: public void run() { vromero@1482: int id = checkCount.incrementAndGet(); vromero@1482: JavaSource source = new JavaSource(id); vromero@1482: JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc, mcimadamore@1336: null, null, Arrays.asList(source)); mcimadamore@1336: Context context = ct.getContext(); mcimadamore@1336: Symtab syms = Symtab.instance(context); mcimadamore@1336: Names names = Names.instance(context); vromero@1452: Types types = Types.instance(context); vromero@1452: ct.addTaskListener(new Indifier(syms, names, types)); mcimadamore@1336: try { mcimadamore@1336: ct.generate(); mcimadamore@1336: } catch (Throwable t) { mcimadamore@1336: t.printStackTrace(); vromero@1482: throw new AssertionError( vromero@1482: String.format("Error thrown when compiling following code\n%s", vromero@1482: source.source)); mcimadamore@1336: } mcimadamore@1336: if (dc.diagFound) { vromero@1482: throw new AssertionError( vromero@1482: String.format("Diags found when compiling following code\n%s\n\n%s", vromero@1482: source.source, dc.printDiags())); mcimadamore@1336: } vromero@1482: verifyBytecode(id); mcimadamore@1336: } mcimadamore@1336: vromero@1482: void verifyBytecode(int id) { vromero@1482: File compiledTest = new File(String.format("Test%d.class", id)); mcimadamore@1336: try { mcimadamore@1336: ClassFile cf = ClassFile.read(compiledTest); mcimadamore@1336: Method testMethod = null; mcimadamore@1336: for (Method m : cf.methods) { mcimadamore@1336: if (m.getName(cf.constant_pool).equals("test")) { mcimadamore@1336: testMethod = m; mcimadamore@1336: break; mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: if (testMethod == null) { mcimadamore@1336: throw new Error("Test method not found"); mcimadamore@1336: } vromero@1482: Code_attribute ea = vromero@1482: (Code_attribute)testMethod.attributes.get(Attribute.Code); mcimadamore@1336: if (testMethod == null) { mcimadamore@1336: throw new Error("Code attribute for test() method not found"); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: int bsmIdx = -1; mcimadamore@1336: mcimadamore@1336: for (Instruction i : ea.getInstructions()) { mcimadamore@1336: if (i.getMnemonic().equals("invokedynamic")) { mcimadamore@1336: CONSTANT_InvokeDynamic_info indyInfo = vromero@1482: (CONSTANT_InvokeDynamic_info)cf vromero@1482: .constant_pool.get(i.getShort(1)); mcimadamore@1336: bsmIdx = indyInfo.bootstrap_method_attr_index; mcimadamore@1336: if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) { vromero@1482: throw new vromero@1482: AssertionError("type mismatch for CONSTANT_InvokeDynamic_info"); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: if (bsmIdx == -1) { mcimadamore@1336: throw new Error("Missing invokedynamic in generated code"); mcimadamore@1336: } mcimadamore@1336: vromero@1482: BootstrapMethods_attribute bsm_attr = vromero@1482: (BootstrapMethods_attribute)cf vromero@1482: .getAttribute(Attribute.BootstrapMethods); mcimadamore@1336: if (bsm_attr.bootstrap_method_specifiers.length != 1) { vromero@1482: throw new Error("Bad number of method specifiers " + vromero@1482: "in BootstrapMethods attribute"); mcimadamore@1336: } mcimadamore@1336: BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = mcimadamore@1336: bsm_attr.bootstrap_method_specifiers[0]; mcimadamore@1336: mcimadamore@1336: if (bsm_spec.bootstrap_arguments.length != arity.arity) { vromero@1482: throw new Error("Bad number of static invokedynamic args " + vromero@1482: "in BootstrapMethod attribute"); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: int count = 0; mcimadamore@1336: for (StaticArgumentKind sak : saks) { vromero@1482: if (!sak.check(cf.constant_pool vromero@1482: .get(bsm_spec.bootstrap_arguments[count]))) { mcimadamore@1336: throw new Error("Bad static argument value " + sak); mcimadamore@1336: } mcimadamore@1336: count++; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: CONSTANT_MethodHandle_info bsm_handle = vromero@1482: (CONSTANT_MethodHandle_info)cf.constant_pool vromero@1482: .get(bsm_spec.bootstrap_method_ref); mcimadamore@1336: mcimadamore@1336: if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) { mcimadamore@1336: throw new Error("Bad kind on boostrap method handle"); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: CONSTANT_Methodref_info bsm_ref = vromero@1482: (CONSTANT_Methodref_info)cf.constant_pool vromero@1482: .get(bsm_handle.reference_index); mcimadamore@1336: mcimadamore@1336: if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) { mcimadamore@1336: throw new Error("Bad owner of boostrap method"); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) { mcimadamore@1336: throw new Error("Bad boostrap method name"); mcimadamore@1336: } mcimadamore@1336: vromero@1482: if (!bsm_ref.getNameAndTypeInfo() vromero@1482: .getType().equals(asBSMSignatureString())) { vromero@1482: throw new Error("Bad boostrap method type" + vromero@1482: bsm_ref.getNameAndTypeInfo().getType() + " " + vromero@1482: asBSMSignatureString()); mcimadamore@1336: } mcimadamore@1336: } catch (Exception e) { mcimadamore@1336: e.printStackTrace(); mcimadamore@1336: throw new Error("error reading " + compiledTest +": " + e); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: String asBSMSignatureString() { mcimadamore@1336: StringBuilder buf = new StringBuilder(); mcimadamore@1336: buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"); mcimadamore@1336: for (StaticArgumentKind sak : saks) { mcimadamore@1336: buf.append(sak.bytecodeTypeStr); mcimadamore@1336: } mcimadamore@1336: buf.append(")Ljava/lang/invoke/CallSite;"); mcimadamore@1336: return buf.toString(); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: class JavaSource extends SimpleJavaFileObject { mcimadamore@1336: mcimadamore@1336: static final String source_template = "import java.lang.invoke.*;\n" + mcimadamore@1336: "class Bootstrap {\n" + vromero@1482: " public static CallSite bsm(MethodHandles.Lookup lookup, " + vromero@1482: "String name, MethodType methodType #SARGS) {\n" + mcimadamore@1336: " return null;\n" + mcimadamore@1336: " }\n" + mcimadamore@1336: "}\n" + vromero@1482: "class Test#ID {\n" + mcimadamore@1336: " void m() { }\n" + mcimadamore@1336: " void test() { m(); }\n" + mcimadamore@1336: "}"; mcimadamore@1336: mcimadamore@1336: String source; mcimadamore@1336: vromero@1482: JavaSource(int id) { mcimadamore@1336: super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); vromero@1482: source = source_template.replace("#SARGS", asSignatureString()) vromero@1482: .replace("#ID", String.valueOf(id)); mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: @Override mcimadamore@1336: public CharSequence getCharContent(boolean ignoreEncodingErrors) { mcimadamore@1336: return source; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: String asSignatureString() { mcimadamore@1336: int count = 0; mcimadamore@1336: StringBuilder buf = new StringBuilder(); mcimadamore@1336: for (StaticArgumentKind sak : saks) { mcimadamore@1336: buf.append(","); mcimadamore@1336: buf.append(sak.sourceTypeStr); mcimadamore@1336: buf.append(' '); mcimadamore@1336: buf.append(String.format("x%d", count++)); mcimadamore@1336: } mcimadamore@1336: return buf.toString(); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: class Indifier extends TreeScanner implements TaskListener { mcimadamore@1336: mcimadamore@1336: MethodSymbol bsm; mcimadamore@1336: Symtab syms; mcimadamore@1336: Names names; vromero@1452: Types types; mcimadamore@1336: vromero@1452: Indifier(Symtab syms, Names names, Types types) { mcimadamore@1336: this.syms = syms; mcimadamore@1336: this.names = names; vromero@1452: this.types = types; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: @Override mcimadamore@1336: public void started(TaskEvent e) { mcimadamore@1336: //do nothing mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: @Override mcimadamore@1336: public void finished(TaskEvent e) { mcimadamore@1336: if (e.getKind() == TaskEvent.Kind.ANALYZE) { mcimadamore@1336: scan(e.getCompilationUnit(), null); mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: @Override mcimadamore@1336: public Void visitMethodInvocation(MethodInvocationTree node, Void p) { mcimadamore@1336: super.visitMethodInvocation(node, p); mcimadamore@1336: JCMethodInvocation apply = (JCMethodInvocation)node; mcimadamore@1336: JCIdent ident = (JCIdent)apply.meth; mcimadamore@1336: Symbol oldSym = ident.sym; mcimadamore@1336: if (!oldSym.isConstructor()) { mcimadamore@1336: Object[] staticArgs = new Object[arity.arity]; mcimadamore@1336: for (int i = 0; i < arity.arity ; i++) { vromero@1452: staticArgs[i] = saks[i].getValue(syms, names, types); mcimadamore@1336: } vromero@1482: ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name, vromero@1482: oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs); mcimadamore@1336: } mcimadamore@1336: return null; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: @Override mcimadamore@1336: public Void visitMethod(MethodTree node, Void p) { mcimadamore@1336: super.visitMethod(node, p); mcimadamore@1336: if (node.getName().toString().equals("bsm")) { mcimadamore@1336: bsm = ((JCMethodDecl)node).sym; mcimadamore@1336: } mcimadamore@1336: return null; mcimadamore@1336: } mcimadamore@1336: } mcimadamore@1336: vromero@1482: static class DiagChecker vromero@1482: implements javax.tools.DiagnosticListener { mcimadamore@1336: mcimadamore@1336: boolean diagFound; mcimadamore@1336: ArrayList diags = new ArrayList<>(); mcimadamore@1336: mcimadamore@1336: public void report(Diagnostic diagnostic) { mcimadamore@1336: diags.add(diagnostic.getMessage(Locale.getDefault())); mcimadamore@1336: diagFound = true; mcimadamore@1336: } mcimadamore@1336: mcimadamore@1336: String printDiags() { mcimadamore@1336: StringBuilder buf = new StringBuilder(); mcimadamore@1336: for (String s : diags) { mcimadamore@1336: buf.append(s); mcimadamore@1336: buf.append("\n"); mcimadamore@1336: } mcimadamore@1336: return buf.toString(); mcimadamore@1336: } mcimadamore@1336: } vromero@1482: mcimadamore@1336: }