1 /* 2 * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 import java.lang.invoke.MethodHandles; 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.net.URI; 29 import java.util.ArrayList; 30 import java.util.Collection; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.concurrent.Callable; 35 import javax.tools.Diagnostic; 36 import javax.tools.DiagnosticCollector; 37 import javax.tools.FileObject; 38 import javax.tools.ForwardingJavaFileManager; 39 import javax.tools.JavaCompiler; 40 import javax.tools.JavaFileManager; 41 import javax.tools.JavaFileObject; 42 import javax.tools.SimpleJavaFileObject; 43 import javax.tools.ToolProvider; 44 45 /** 46 * This program tries to compile a large number of classes that exercise a fair amount of 47 * features in javac. 48 */ 49 public class JavacBenchApp { 50 static class ClassFile extends SimpleJavaFileObject { 51 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 52 protected ClassFile(String name) { 53 super(URI.create("memo:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS); 54 } 55 @Override 56 public ByteArrayOutputStream openOutputStream() { 57 return this.baos; 58 } 59 byte[] toByteArray() { 60 return baos.toByteArray(); 61 } 62 } 63 64 static class FileManager extends ForwardingJavaFileManager<JavaFileManager> { 65 private Map<String, ClassFile> classesMap = new HashMap<String, ClassFile>(); 66 protected FileManager(JavaFileManager fileManager) { 67 super(fileManager); 68 } 69 @Override 70 public ClassFile getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) { 71 ClassFile classFile = new ClassFile(name); 72 classesMap.put(name, classFile); 73 return classFile; 74 } 75 public Map<String, byte[]> getCompiledClasses() { 76 Map<String, byte[]> result = new HashMap<>(); 77 for (Map.Entry<String, ClassFile> entry : classesMap.entrySet()) { 78 result.put(entry.getKey(), entry.getValue().toByteArray()); 79 } 80 return result; 81 } 82 } 83 84 static class SourceFile extends SimpleJavaFileObject { 85 private CharSequence sourceCode; 86 public SourceFile(String name, CharSequence sourceCode) { 87 super(URI.create("memo:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 88 this.sourceCode = sourceCode; 89 } 90 @Override 91 public CharSequence getCharContent(boolean ignore) { 92 return this.sourceCode; 93 } 94 } 95 96 public Map<String, byte[]> compile() { 97 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 98 DiagnosticCollector<JavaFileObject> ds = new DiagnosticCollector<>(); 99 Collection<SourceFile> sourceFiles = sources; 100 101 try (FileManager fileManager = new FileManager(compiler.getStandardFileManager(ds, null, null))) { 102 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sourceFiles); 103 if (task.call()) { 104 return fileManager.getCompiledClasses(); 105 } else { 106 for (Diagnostic<? extends JavaFileObject> d : ds.getDiagnostics()) { 107 System.out.format("Line: %d, %s in %s", d.getLineNumber(), d.getMessage(null), d.getSource().getName()); 108 } 109 throw new InternalError("compilation failure"); 110 } 111 } catch (IOException e) { 112 throw new InternalError(e); 113 } 114 } 115 116 List<SourceFile> sources; 117 118 static final String imports = """ 119 import java.lang.*; 120 import java.util.*; 121 """; 122 123 static final String testClassBody = """ 124 // Some comments 125 static long x; 126 static final long y; 127 static { 128 y = System.currentTimeMillis(); 129 } 130 /* More comments */ 131 @Deprecated 132 String func() { return "String " + this + y; } 133 public static void main(String args[]) { 134 try { 135 x = Long.parseLong(args[0]); 136 } catch (Throwable t) { 137 t.printStackTrace(); 138 } 139 doit(() -> { 140 System.out.println("Hello Lambda"); 141 Thread.dumpStack(); 142 }); 143 } 144 static List<String> list = List.of("1", "2"); 145 class InnerClass1 { 146 static final long yy = y; 147 } 148 static void doit(Runnable r) { 149 for (var x : list) { 150 r.run(); 151 } 152 } 153 static String patternMatch(String arg, Object o) { 154 if (o instanceof String s) { 155 return "1234"; 156 } 157 final String b = "B"; 158 return switch (arg) { 159 case "A" -> "a"; 160 case b -> "b"; 161 default -> "c"; 162 }; 163 } 164 public sealed class SealedInnerClass {} 165 public final class Foo extends SealedInnerClass {} 166 enum Expression { 167 ADDITION, 168 SUBTRACTION, 169 MULTIPLICATION, 170 DIVISION 171 } 172 public record Point(int x, int y) { 173 public Point(int x) { 174 this(x, 0); 175 } 176 } 177 """; 178 179 String sanitySource = """ 180 public class Sanity implements java.util.concurrent.Callable<String> { 181 public String call() { 182 return "this is a test"; 183 } 184 } 185 """; 186 187 void setup(int count) { 188 sources = new ArrayList<>(count); 189 for (int i = 0; i < count; i++) { 190 String source = imports + "public class Test" + i + " {" + testClassBody + "}"; 191 sources.add(new SourceFile("Test" + i, source)); 192 } 193 194 sources.add(new SourceFile("Sanity", sanitySource)); 195 } 196 197 @SuppressWarnings("unchecked") 198 static void validate(byte[] sanityClassFile) throws Throwable { 199 MethodHandles.Lookup lookup = MethodHandles.lookup(); 200 Class<?> cls = lookup.defineClass(sanityClassFile); 201 Callable<String> obj = (Callable<String>)cls.getDeclaredConstructor().newInstance(); 202 String s = obj.call(); 203 if (!s.equals("this is a test")) { 204 throw new RuntimeException("Expected \"this is a test\", but got \"" + s + "\""); 205 } 206 } 207 208 public static void main(String args[]) throws Throwable { 209 long started = System.currentTimeMillis(); 210 JavacBenchApp bench = new JavacBenchApp(); 211 212 int count = 0; 213 if (args.length > 0) { 214 count = Integer.parseInt(args[0]); 215 if (count >= 0) { 216 bench.setup(count); 217 Map<String, byte[]> allClasses = bench.compile(); 218 validate(allClasses.get("Sanity")); 219 } 220 } 221 if (System.getProperty("JavacBenchApp.silent") == null) { 222 // Set this property when running with "perf stat", etc 223 long elapsed = System.currentTimeMillis() - started; 224 System.out.println("Generated source code for " + bench.sources.size() + " classes and compiled them in " + elapsed + " ms"); 225 } 226 } 227 } 228