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