tools/javac/SourceGenerator.java (106 lines of code) (raw):

/* * Copyright 2000-2024 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Generates main class "JBR". */ public class SourceGenerator { private final ProcessingEnvironment processingEnv; private final String jbrTemplate, serviceGetterTemplate; public SourceGenerator(ProcessingEnvironment processingEnv) { this.processingEnv = processingEnv; try { jbrTemplate = Files.readString(Path.of("tools/templates/JBR.java")); serviceGetterTemplate = Files.readString(Path.of("tools/templates/service-getter.txt")); } catch (IOException e) { throw new RuntimeException(e); } } public void generate(Round round) { Set<? extends Element> serviceElements = round.getElementsAnnotatedWith(round.annotations.service), providedElements = round.getElementsAnnotatedWith(round.annotations.provided); Map<String, Set<TypeElement>> extensions = findExtensions(round); // Generate JBR class source code. List<String> serviceGetters = serviceElements.stream() .filter(e -> // Only top-level public interfaces are included. e.getEnclosingElement().getKind() == ElementKind.PACKAGE && e.getModifiers().contains(Modifier.PUBLIC)) .map(s -> generateServiceGetter(round, s)).toList(); List<String> knownExtensions = extensions.entrySet().stream() .map(e -> "KNOWN_EXTENSIONS.put(Extensions." + e.getKey() + ", new Class[] {" + e.getValue().stream().map(c -> c.getQualifiedName() + ".class") .collect(Collectors.joining(", ")) + "});").toList(); String result = replaceTemplate( replaceTemplate(jbrTemplate, "/*GENERATED_METHODS*/", serviceGetters, true), "/*KNOWN_EXTENSIONS*/", knownExtensions, false) .replace("/*KNOWN_PROXIES*/", joinClassNamesToList(providedElements)) .replace("/*KNOWN_SERVICES*/", joinClassNamesToList(serviceElements)); // Write generated content. try { JavaFileObject file = processingEnv.getFiler().createSourceFile("jetbrains.runtime.api/com.jetbrains.JBR", Stream.concat(serviceElements.stream(), providedElements.stream()).toArray(Element[]::new)); try (Writer w = file.openWriter()) { w.write(result); } } catch (IOException e) { throw new RuntimeException(e); } } private String generateServiceGetter(Round round, Element service) { String fallback = round.getFallbackName(service); String javadoc = processingEnv.getElementUtils().getDocComment(service); if (javadoc != null) javadoc = "\n *" + javadoc.replaceAll("\n", "\n *"); else javadoc = ""; Deprecated deprecated = service.getAnnotation(Deprecated.class); String deprecation; if (deprecated == null) deprecation = ""; else if (!deprecated.forRemoval()) deprecation = "\n" + deprecated; else deprecation = "\n@SuppressWarnings(\"removal\")\n" + deprecated; return serviceGetterTemplate .replace("<FALLBACK>", fallback != null ? fallback + "::new" : "null") .replaceAll("\\$", service.getSimpleName().toString()) .replace("<JAVADOC>", javadoc) .replaceAll("<DEPRECATED>", deprecation); } private String joinClassNamesToList(Set<? extends Element> elements) { return elements.stream() .map(e -> "\"" + processingEnv.getElementUtils().getBinaryName((TypeElement) e) + "\"") .collect(Collectors.joining(", ")); } @SuppressWarnings("SameParameterValue") private static String replaceTemplate(String src, String placeholder, Iterable<String> statements, boolean space) { int placeholderIndex = src.indexOf(placeholder); int indent = 0; while (placeholderIndex - indent >= 1 && src.charAt(placeholderIndex - indent - 1) == ' ') indent++; int nextLineIndex = src.indexOf('\n', placeholderIndex + placeholder.length()) + 1; if (nextLineIndex == 0) nextLineIndex = placeholderIndex + placeholder.length(); String before = src.substring(0, placeholderIndex - indent), after = src.substring(nextLineIndex); StringBuilder sb = new StringBuilder(before); boolean firstStatement = true; for (String s : statements) { if (!firstStatement && space) sb.append('\n'); sb.append(s.indent(indent)); firstStatement = false; } sb.append(after); return sb.toString(); } private static Map<String, Set<TypeElement>> findExtensions(Round round) { if (round.annotations.extension == null) return Map.of(); Set<? extends Element> extensionElements = round.getElementsAnnotatedWith(round.annotations.extension); Map<String, Set<TypeElement>> map = new HashMap<>(); for (Element method : extensionElements) { String extension = round.getExtensionName(method); if (extension != null) { map.computeIfAbsent(extension, s -> new HashSet<>()).add((TypeElement) method.getEnclosingElement()); } } return map; } }