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 javax.tools.*;
 26 import java.io.*;
 27 import java.net.URI;
 28 import java.util.*;
 29 
 30 /**
 31  * This program tries to compile a large number of classes that exercise a fair amount of
 32  * features in javac.
 33  */
 34 public class JavacBenchApp {
 35     static class ClassFile extends SimpleJavaFileObject {
 36         private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 37         protected ClassFile(String name) {
 38             super(URI.create("memo:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
 39         }
 40         @Override
 41         public ByteArrayOutputStream openOutputStream() {
 42             return this.baos;
 43         }
 44         byte[] toByteArray() {
 45             return baos.toByteArray();
 46         }
 47     }
 48 
 49     static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
 50         private Map<String, ClassFile> classesMap = new HashMap<String, ClassFile>();
 51         protected FileManager(JavaFileManager fileManager) {
 52             super(fileManager);
 53         }
 54         @Override
 55         public ClassFile getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) {
 56             ClassFile classFile = new ClassFile(name);
 57             classesMap.put(name, classFile);
 58             return classFile;
 59         }
 60         public Map<String, byte[]> getByteCode() {
 61             Map<String, byte[]> result = new HashMap<>();
 62             for (Map.Entry<String, ClassFile> entry : classesMap.entrySet()) {
 63                 result.put(entry.getKey(), entry.getValue().toByteArray());
 64             }
 65             return result;
 66         }
 67     }
 68 
 69     static class SourceFile extends SimpleJavaFileObject {
 70         private CharSequence sourceCode;
 71         public SourceFile(String name, CharSequence sourceCode) {
 72             super(URI.create("memo:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
 73             this.sourceCode = sourceCode;
 74         }
 75         @Override
 76         public CharSequence getCharContent(boolean ignore) {
 77             return this.sourceCode;
 78         }
 79     }
 80 
 81     public Object compile(int count) {
 82         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 83         DiagnosticCollector<JavaFileObject> ds = new DiagnosticCollector<>();
 84         Collection<SourceFile> sourceFiles = sources.subList(0, count);
 85 
 86         try (FileManager fileManager = new FileManager(compiler.getStandardFileManager(ds, null, null))) {
 87             JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sourceFiles);
 88             if (task.call()) {
 89                 return fileManager.getByteCode();
 90             } else {
 91                 for (Diagnostic<? extends JavaFileObject> d : ds.getDiagnostics()) {
 92                   System.out.format("Line: %d, %s in %s", d.getLineNumber(), d.getMessage(null), d.getSource().getName());
 93                   }
 94                 throw new InternalError("compilation failure");
 95             }
 96         } catch (IOException e) {
 97             throw new InternalError(e);
 98         }
 99     }
100 
101     List<SourceFile> sources;
102 
103     static final String imports = """
104         import java.lang.*;
105         import java.util.*;
106         """;
107 
108     static final String testClassBody = """
109         // Some comments
110         static long x;
111         static final long y;
112         static {
113             y = System.currentTimeMillis();
114         }
115         /* More comments */
116         @Deprecated
117         String func() { return "String " + this + y; }
118         public static void main(String args[]) {
119             try {
120                 x = Long.parseLong(args[0]);
121             } catch (Throwable t) {
122                 t.printStackTrace();
123             }
124             doit(() -> {
125                 System.out.println("Hello Lambda");
126                 Thread.dumpStack();
127             });
128         }
129         static List<String> list = List.of("1", "2");
130         class InnerClass1 {
131             static final long yy = y;
132         }
133         static void doit(Runnable r) {
134             for (var x : list) {
135                 r.run();
136             }
137         }
138         static String patternMatch(String arg, Object o) {
139             if (o instanceof String s) {
140                 return "1234";
141             }
142             final String b = "B";
143             return switch (arg) {
144                 case "A" -> "a";
145                 case b   -> "b";
146                 default  -> "c";
147             };
148         }
149         public sealed class SealedInnerClass {}
150         public final class Foo extends SealedInnerClass {}
151         enum Expression {
152             ADDITION,
153             SUBTRACTION,
154             MULTIPLICATION,
155             DIVISION
156         }
157         public record Point(int x, int y) {
158             public Point(int x) {
159                 this(x, 0);
160             }
161         }
162         """;
163 
164     List<SourceFile> generate(int count) {
165         ArrayList<SourceFile> sources = new ArrayList<>(count);
166         for (int i = 0; i < count; i++) {
167             String source = imports + "public class Test" + i + " {" + testClassBody + "}";
168             sources.add(new SourceFile("Test" + i, source));
169         }
170         return sources;
171     }
172 
173     public void setup() {
174         sources = generate(10_000);
175     }
176 
177     public static void main(String args[]) throws Throwable {
178         long started = System.currentTimeMillis();
179         JavacBenchApp bench = new JavacBenchApp();
180         bench.setup();
181         int count = 0;
182         if (args.length > 0) {
183             count = Integer.parseInt(args[0]);
184             if (count > 0) {
185                 bench.compile(count);
186             }
187         }
188         long elapsed = System.currentTimeMillis() - started;
189         if (System.getProperty("JavacBenchApp.silent") == null) {
190             // Set this property when running with "perf stat", etc
191             System.out.println("Generated source code for " + bench.sources.size() + " classes and compiled them for " + count + " times in " + elapsed + " ms");
192         }
193     }
194 }
195