diff --git a/build-logic/ide/build.gradle.kts b/build-logic/ide/build.gradle.kts index 82139f2f0..29aff7720 100644 --- a/build-logic/ide/build.gradle.kts +++ b/build-logic/ide/build.gradle.kts @@ -46,4 +46,6 @@ dependencies { implementation(libs.common.antlr4) implementation(libs.google.gson) implementation(libs.google.java.format) + + implementation(libs.misc.jackpot30) } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/JavacJackpotPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/JavacJackpotPlugin.kt new file mode 100644 index 000000000..c99630732 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/JavacJackpotPlugin.kt @@ -0,0 +1,149 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins + +import com.itsaky.androidide.plugins.tasks.javac.JavacExtractClassesTask +import com.itsaky.androidide.plugins.tasks.javac.JavacFinalizeTask +import com.itsaky.androidide.plugins.tasks.javac.JavacJackpotTask +import com.itsaky.androidide.plugins.tasks.javac.JavacPropertiesParserTask +import com.itsaky.androidide.plugins.tasks.javac.JavacRenamePackageReferencesTask +import com.itsaky.androidide.plugins.tasks.javac.JdkSourceDownloadTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.Delete +import org.gradle.api.tasks.compile.JavaCompile + +/** + * Downloads JDK source, extracts source files for the Java compiler and patches them so that + * they can be used on Android. + * + * @author Akash Yadav + */ +class JavacJackpotPlugin : Plugin { + + companion object { + + /** + * Files to copy as-is from JDK source. The keys are the JDK module names and the values are + * glob paths to the files in /share/classes directory, minus the '.java' extension. + * + * IMPORTANT: Package names of all the classes are prefixed with `com.itsaky.androidide` in order + * to avoid conflicts with the classes defined in the JDK. + */ + private val copySourceFiles = listOf( + "java.base" to "jdk/internal/misc/PreviewFeatures", + "java.base" to "jdk/internal/misc/MainMethodFinder", + "java.base" to "jdk/internal/javac/*", + ) + } + + override fun apply(target: Project) { + target.run { + val javacAndroid = layout.buildDirectory.dir("javac-android") + val jdkSource = javacAndroid.get().dir("jdk") + + val downloadTask = tasks.register("downloadJdkSource", JdkSourceDownloadTask::class.java) + + val deleteExtractedSourceTask = tasks.register("deleteExtractedJdkSource", + Delete::class.java) { + dependsOn(downloadTask) + delete(jdkSource) + } + + val extractJdkSourceTask = tasks.register("extractJdkSource", Copy::class.java) { + dependsOn(deleteExtractedSourceTask) + + from(tarTree(downloadTask.get().outputFile)) + into(jdkSource) + } + + val jackpotTaskName = "javacJackpot" + val transformedJavacSourceDir = javacAndroid.get().dir(jackpotTaskName) + + val deleteTransformedSourcesTask = tasks.register( + "deleteTransformedJavacSources", + Delete::class.java + ) { + dependsOn(extractJdkSourceTask) + delete(transformedJavacSourceDir) + } + + val copiedSourceFilesDir = javacAndroid.get().dir("copiedSources") + val copySourcesTask = tasks.register("copySourceFiles", Copy::class.java) { + dependsOn(deleteTransformedSourcesTask) + + copySourceFiles.forEach { (module, files) -> + from( + jdkSource.dir(downloadTask.get().jdkSourceDirName()).dir("src/${module}/share/classes") + ) + include("${files}.java") + } + + into(copiedSourceFilesDir.dir("com/itsaky/androidide")) + } + + val jackpotTask = tasks.register(jackpotTaskName, JavacJackpotTask::class.java) { + dependsOn(copySourcesTask) + source(project.fileTree("src/main/resources").matching { include("**/*.hint") }) + jdkSourceDirectory.set(jdkSource.dir(downloadTask.get().jdkSourceDirName())) + sourceOutDirectory.set(transformedJavacSourceDir) + } + + val deleteInfoFilesTask = tasks.register("deleteInfoFiles", Delete::class.java) { + delete(project.fileTree(transformedJavacSourceDir).also { + dependsOn(jackpotTask) + it.include("**/module-info.java", "**/package-info.java") + }) + } + + val renamePackageReferencesTask = tasks.register("renamePackageReferences", + JavacRenamePackageReferencesTask::class.java) { + dependsOn(deleteInfoFilesTask) + source(transformedJavacSourceDir.asFileTree.matching { include("**/*.java") }) + source(copiedSourceFilesDir) + } + + val compilePropertiesTask = target.tasks.register("compileProperties", + JavacPropertiesParserTask::class.java) { + dependsOn(renamePackageReferencesTask) + sourceDir.set(transformedJavacSourceDir.dir("jdk.compiler")) + outputs.dir(getPropsDir()) + } + + val finalTask = tasks.register("finalizeJavacTask", JavacFinalizeTask::class.java) { + dependsOn(compilePropertiesTask) + } + + tasks.withType(JavaCompile::class.java) { + dependsOn(finalTask) + + options.compilerArgs.add("--add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED") + } + + extensions.getByType(JavaPluginExtension::class.java).run { + sourceSets.getByName("main").java.srcDirs( + JavacJackpotTask.getTransformedSourceDirectories(transformedJavacSourceDir.asFile), + copiedSourceFilesDir, + compilePropertiesTask.get().getPropsDir() + ) + } + } + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt index 022b62547..124e1553c 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/PropertiesParserPlugin.kt @@ -41,32 +41,32 @@ class PropertiesParserPlugin : Plugin { sourceSets.getByName("main").java { srcDir(target.propsDir) } } - target.tasks.register("compileProperties") { - doLast { - val options = target.createParserOptions() - if (options.isEmpty()) { - return@doLast - } - - val parser = PropertiesParser { target.logger.lifecycle(it) } - - logger.info( - "Running PropertiesParser with arguments: ${options.joinToString(separator = " ")}" - ) - - val ok = parser.run(options.toTypedArray()) - if (!ok) { - throw GradleException("Failed to parse property files") - } - } - - outputs.dir(target.propsDir) - } - - target.tasks.register("cleanCompiledProperties", Delete::class.java) { delete(target.propsDir) } - - target.tasks.withType(JavaCompile::class.java) { dependsOn("compileProperties") } - target.tasks.getByName("clean").dependsOn("cleanCompiledProperties") +// target.tasks.register("compileProperties") { +// doLast { +// val options = target.createParserOptions() +// if (options.isEmpty()) { +// return@doLast +// } +// +// val parser = PropertiesParser { target.logger.lifecycle(it) } +// +// logger.info( +// "Running PropertiesParser with arguments: ${options.joinToString(separator = " ")}" +// ) +// +// val ok = parser.run(options.toTypedArray()) +// if (!ok) { +// throw GradleException("Failed to parse property files") +// } +// } +// +// outputs.dir(target.propsDir) +// } +// +// target.tasks.register("cleanCompiledProperties", Delete::class.java) { delete(target.propsDir) } +// +// target.tasks.withType(JavaCompile::class.java) { dependsOn("compileProperties") } +// target.tasks.getByName("clean").dependsOn("cleanCompiledProperties") } } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt index b2dd41fa0..b4c1bcd9f 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/conf/AndroidModuleConf.kt @@ -72,6 +72,7 @@ fun Project.configureAndroidModule( arrayOf( "META-INF/CHANGES", "META-INF/README.md", + "nbjavac.hint" ) ) pickFirsts.addAll( diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java index 6a4aef5f1..f79929b3a 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/PropertiesParser.java @@ -46,20 +46,11 @@ public class PropertiesParser { public Logger logger; - public PropertiesParser(Logger logger) { + private final File baseDir; + + public PropertiesParser(Logger logger, File baseDir) { this.logger = logger; - } - - public static void main(String[] args) { - boolean ok = run(args, System.out); - if (!ok) { - System.exit(1); - } - } - - public static boolean run(String[] args, PrintStream out) { - PropertiesParser pp = new PropertiesParser(out::println); - return pp.run(args); + this.baseDir = baseDir; } public void info(String msg) { @@ -86,7 +77,7 @@ public class PropertiesParser { File propertyFile = new File(propertyPath); String prefix = propertyFile.getName().split("\\.")[0]; MessageFile messageFile = new MessageFile(propertyFile, prefix); - new ClassGenerator().generateFactory(messageFile, new File(outPath)); + new ClassGenerator(this.baseDir).generateFactory(messageFile, new File(outPath)); } catch (Throwable ex) { throw new RuntimeException(ex); } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java index 52ac948f2..1fc3e0f10 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/gen/ClassGenerator.java @@ -57,6 +57,8 @@ import java.util.stream.Stream; public class ClassGenerator { + private final File baseDir; + private static final String TEMPLATE_PROPS = "#\n" + "# Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.\n" + "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" + @@ -86,10 +88,10 @@ public class ClassGenerator { " package {0};\\n\\\n" + " \\n\\\n" + " {1}\\n\\\n" + - " import openjdk.tools.javac.util.JCDiagnostic.Error;\\n\\\n" + - " import openjdk.tools.javac.util.JCDiagnostic.Warning;\\n\\\n" + - " import openjdk.tools.javac.util.JCDiagnostic.Note;\\n\\\n" + - " import openjdk.tools.javac.util.JCDiagnostic.Fragment;\\n\\\n" + + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Error;\\n\\\n" + + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Warning;\\n\\\n" + + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Note;\\n\\\n" + + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Fragment;\\n\\\n" + " \\n\\\n" + " public class {2} '{'\\n\\\n" + " {3}\\n\\\n" + @@ -234,6 +236,10 @@ public class ClassGenerator { } }; + public ClassGenerator(File baseDir) { + this.baseDir = baseDir; + } + /** * Form the name of the toplevel factory class. */ @@ -337,9 +343,7 @@ public class ClassGenerator { */ String packageName(File file) { String path = file.getAbsolutePath(); - int begin = path.indexOf("src/main/resources".replace('/', File.separatorChar)) - + "src/main/resources".length(); - String packagePath = path.substring(begin + 1, path.lastIndexOf(File.separatorChar)); + String packagePath = path.substring(baseDir.getAbsolutePath().length() + 1, path.lastIndexOf(File.separatorChar)); return packagePath.replace(File.separatorChar, '.'); } diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java index 34a08e1cd..64cd086c2 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/properties/parser/MessageType.java @@ -46,30 +46,30 @@ public interface MessageType { */ enum SimpleType implements MessageType { - ANNOTATION("annotation", "Compound", "openjdk.tools.javac.code.Attribute"), + ANNOTATION("annotation", "Compound", "com.itsaky.androidide.com.sun.tools.javac.code.Attribute"), BOOLEAN("boolean", "boolean", null), COLLECTION("collection", "Collection", "java.util"), - FLAG("flag", "Flag", "openjdk.tools.javac.code.Flags"), + FLAG("flag", "Flag", "com.itsaky.androidide.com.sun.tools.javac.code.Flags"), FRAGMENT("fragment", "Fragment", null), - DIAGNOSTIC("diagnostic", "JCDiagnostic", "openjdk.tools.javac.util"), - MODIFIER("modifier", "Modifier", "jdkx.lang.model.element"), + DIAGNOSTIC("diagnostic", "JCDiagnostic", "com.itsaky.androidide.com.sun.tools.javac.util"), + MODIFIER("modifier", "Modifier", "com.itsaky.androidide.javax.lang.model.element"), FILE("file", "File", "java.io"), - FILE_OBJECT("file object", "JavaFileObject", "jdkx.tools"), + FILE_OBJECT("file object", "JavaFileObject", "com.itsaky.androidide.javax.tools"), PATH("path", "Path", "java.nio.file"), - NAME("name", "Name", "openjdk.tools.javac.util"), + NAME("name", "Name", "com.itsaky.androidide.com.sun.tools.javac.util"), NUMBER("number", "int", null), - OPTION_NAME("option name", "Option", "openjdk.tools.javac.main"), - PROFILE("profile", "Profile", "openjdk.tools.javac.jvm"), - SOURCE("source", "Source", "openjdk.tools.javac.code"), - SOURCE_VERSION("source version", "SourceVersion", "jdkx.lang.model"), + OPTION_NAME("option name", "Option", "com.itsaky.androidide.com.sun.tools.javac.main"), + PROFILE("profile", "Profile", "com.itsaky.androidide.com.sun.tools.javac.jvm"), + SOURCE("source", "Source", "com.itsaky.androidide.com.sun.tools.javac.code"), + SOURCE_VERSION("source version", "SourceVersion", "com.itsaky.androidide.javax.lang.model"), STRING("string", "String", null), - SYMBOL("symbol", "Symbol", "openjdk.tools.javac.code"), - SYMBOL_KIND("symbol kind", "Kind", "openjdk.tools.javac.code.Kinds"), - KIND_NAME("kind name", "KindName", "openjdk.tools.javac.code.Kinds"), - TARGET("target", "Target", "openjdk.tools.javac.jvm"), - TOKEN("token", "TokenKind", "openjdk.tools.javac.parser.Tokens"), - TREE_TAG("tree tag", "Tag", "openjdk.tools.javac.tree.JCTree"), - TYPE("type", "Type", "openjdk.tools.javac.code"), + SYMBOL("symbol", "Symbol", "com.itsaky.androidide.com.sun.tools.javac.code"), + SYMBOL_KIND("symbol kind", "Kind", "com.itsaky.androidide.com.sun.tools.javac.code.Kinds"), + KIND_NAME("kind name", "KindName", "com.itsaky.androidide.com.sun.tools.javac.code.Kinds"), + TARGET("target", "Target", "com.itsaky.androidide.com.sun.tools.javac.jvm"), + TOKEN("token", "TokenKind", "com.itsaky.androidide.com.sun.tools.javac.parser.Tokens"), + TREE_TAG("tree tag", "Tag", "com.itsaky.androidide.com.sun.tools.javac.tree.JCTree"), + TYPE("type", "Type", "com.itsaky.androidide.com.sun.tools.javac.code"), URL("url", "URL", "java.net"), SET("set", "Set", "java.util"), LIST("list", "List", "java.util"), @@ -114,7 +114,7 @@ public interface MessageType { * handy option when prototyping - but usages of custom types should be avoided in product-quality * resource file comments. *

- * Example: 'openjdk.tools.javac.code.Flags.Flag' + * Example: 'com.itsaky.androidide.com.sun.tools.javac.code.Flags.Flag' */ class CustomType implements MessageType { diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacExtractClassesTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacExtractClassesTask.kt new file mode 100644 index 000000000..8c833ea3f --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacExtractClassesTask.kt @@ -0,0 +1,61 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.net.URI +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.io.path.copyTo + +/** + * @author Akash Yadav + */ +abstract class JavacExtractClassesTask : DefaultTask() { + + @get:Input + abstract val classFiles: ListProperty> + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun extractClasses() { + val modules = Paths.get(URI.create("jrt:/")).resolve("/modules") + + val outDir = outputDirectory.get().asFile + + for ((module, classFile) in classFiles.get()) { + val classPath = "${classFile}.class" + val path = modules.resolve(module).resolve(classPath) + if (!Files.exists(path)) { + throw GradleException("Class file $classPath does not exist in module $module") + } + + val target = outDir.toPath().resolve(classPath) + Files.createDirectories(target.parent) + path.copyTo(target) + } + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacFinalizeTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacFinalizeTask.kt new file mode 100644 index 000000000..a50624fef --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacFinalizeTask.kt @@ -0,0 +1,33 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +/** + * No-op, marker task. + * + * @author Akash Yadav + */ +abstract class JavacFinalizeTask : DefaultTask() { + + @TaskAction + fun noOp() { + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacJackpotTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacJackpotTask.kt new file mode 100644 index 000000000..301177ab7 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacJackpotTask.kt @@ -0,0 +1,178 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.SourceTask +import org.gradle.api.tasks.TaskAction +import java.io.File + +/** + * Task to transform Java compiler sources to make it compatible with the Android Runtime. + * + * @author Akash Yadav + */ +abstract class JavacJackpotTask : SourceTask() { + + /** + * The source directory for the JDK. + */ + @get:InputDirectory + abstract val jdkSourceDirectory: DirectoryProperty + + /** + * The output directory where all the transformed sources will be stored. + */ + @get:OutputDirectory + @get:Optional + abstract val sourceOutDirectory: DirectoryProperty + + init { + run { + sourceOutDirectory.convention(project.layout.buildDirectory.dir(name)) + } + } + + companion object { + + private val javaPckRegex = """^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*${'$'}""".toRegex() + private val packageTransformExcludes = arrayOf( + "sun.reflect" + ) + + fun getTransformedSourceDirectories(sourceOutDir: File): List { + return compilerModules().map { resolveTransformedSrcDir(sourceOutDir, it, false) } + } + + private fun compilerModules(): Array { + return arrayOf("java.compiler", "jdk.compiler") + } + + private fun resolveTransformedSrcDir(srcOut: File, module: String, transformPck: Boolean) = + srcOut.resolve(module).let { + if (transformPck) { + File(it, "com/itsaky/androidide") + } else it + } + + private fun resolveJdkModuleJavaSrc(jdkSrc: File, module: String) = + jdkSrc.resolve("src/${module}/share/classes") + } + + @TaskAction + fun jackpot() { + val projectJavaSrc = project.file("src/main/java") + val jdkSrc = jdkSourceDirectory.get().asFile + val srcOut = sourceOutDirectory.get().asFile + val modules = compilerModules() + + for (module in modules) { + resolveJdkModuleJavaSrc(jdkSrc, module).copyRecursively( + resolveTransformedSrcDir(srcOut, module, true)) + } + + val sourcePaths = mutableListOf(projectJavaSrc.absolutePath).let { paths -> + paths.addAll( + modules.map { module -> resolveJdkModuleJavaSrc(jdkSrc, module).absolutePath } + ) + paths.joinToString(separator = File.pathSeparator) + } + + // sorted + val finalHintFiles = source.files.map { it.absolutePath }.toMutableList() + finalHintFiles.add(writePckTransformationsHintFile(srcOut)) + + finalHintFiles.sortByDescending { it.substringAfterLast(File.separatorChar).substring(0, 2) } + + for (hintFile in finalHintFiles) { + val cmdline = createCmdlineForHintFile( + hintFile, + sourcePaths, + modules, + srcOut + ) + + logger.lifecycle("Running: ${cmdline.joinToString(separator = " ")}") + org.netbeans.modules.jackpot30.cmdline.Main.compile(*cmdline.toTypedArray()) + } + } + + private fun createCmdlineForHintFile(hintFilePath: String, sourcePaths: String, + modules: Array, + srcOut: File): MutableList { + val cmdline = mutableListOf( + "--hint-file", + hintFilePath, + "--sourcepath", + sourcePaths, + "--source", "8", + "--apply" + ) + + cmdline.addAll(modules.map { module -> + resolveTransformedSrcDir(srcOut, module, false).also { it.mkdirs() }.absolutePath + }) + return cmdline + } + + private fun writePckTransformationsHintFile(srcOut: File): String { + val hintFileOut = sourceOutDirectory.file("ac_nb-javac-android.hint").get().asFile + val packgeTransformations = compilerModules().flatMap { module -> + val moduleDir = resolveTransformedSrcDir(srcOut, module, true) + + // list all classes and change the refactor to change their package names + // refactoring the package names directly causes strange issues and in some cases, + // a few classes are left out + // as a result, we refactor each class individually and change the package/import declarations + // manually using find & replace + moduleDir.walkBottomUp() + .filter { it.isFile && it.extension == "java" } + .mapNotNull { + it.absolutePath.substring(moduleDir.absolutePath.length) + .takeIf { path -> path.isNotBlank() } + } + .map { it.substring(1).replace(File.separatorChar, '.') } + .map { it.substringBeforeLast(".java") } + .distinct() + + // to change the package names directly, + // use the following +// moduleDir.walkBottomUp() +// .filter { it.isDirectory } +// .mapNotNull { +// it.absolutePath.substring(moduleDir.absolutePath.length) +// .takeIf { path -> path.isNotBlank() } +// } +// .map { it.substring(1).replace(File.separatorChar, '.') } +// .distinct() +// .filter { pck -> javaPckRegex.matches(pck) && packageTransformExcludes.none { it == pck || it.startsWith(pck) } } +// }.joinToString(separator = System.lineSeparator()) { pck -> +// "$pck => com.itsaky.androidide.${pck} ;;" +// } + }.joinToString(separator = System.lineSeparator()) { cls -> + "$cls => com.itsaky.androidide.${cls} ;;" + } + + hintFileOut.writeText(packgeTransformations) + + return hintFileOut.absolutePath + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacPropertiesParserTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacPropertiesParserTask.kt new file mode 100644 index 000000000..5ee2126e2 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacPropertiesParserTask.kt @@ -0,0 +1,97 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import com.itsaky.androidide.plugins.properties.PropertiesParser +import com.itsaky.androidide.plugins.properties.gen.ClassGenerator +import com.itsaky.androidide.plugins.tasks.javac.JavacPropertiesParserTask.Companion.getGeneratedPropsDirectory +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import java.io.File + +/** + * @author Akash Yadav + */ +abstract class JavacPropertiesParserTask : DefaultTask() { + + @get:Internal + abstract val sourceDir: DirectoryProperty + + @TaskAction + fun parseProperties() { + val options = project.createParserOptions(sourceDir.get().asFile) + if (options.isEmpty()) { + return + } + + val parser = PropertiesParser({ logger.lifecycle(it) }, sourceDir.get().asFile) + + logger.info( + "Running PropertiesParser with arguments: ${options.joinToString(separator = " ")}" + ) + + val ok = parser.run(options.toTypedArray()) + if (!ok) { + throw GradleException("Failed to parse property files") + } + } + + @Internal + fun getPropsDir(): File { + return project.getGeneratedPropsDirectory() + } + + companion object { + + fun Project.getGeneratedPropsDirectory(): File = + project.layout.buildDirectory.dir("generated/properties").get().asFile + } +} + +private fun Project.createParserOptions(baseDir: File): List { + val options = mutableListOf() + baseDir.walkTopDown().forEach { + if (!(it.isFile && it.extension == "properties")) { + return@forEach + } + + val destPath = + it.path.substringAfter(baseDir.absolutePath) + .substringBeforeLast(File.separatorChar) + + File.separator + + ClassGenerator.toplevelName(it) + + ".java" + + val destFile = File(getGeneratedPropsDirectory(), destPath) + + if (destFile.exists() && destFile.lastModified() >= it.lastModified()) { + logger.info("${it.name} is up-to-date. Skipping.") + return@forEach + } + + destFile.parentFile.mkdirs() + options.add("-compile") + options.add(it.absolutePath) + options.add(destFile.parentFile.absolutePath) + } + return options +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackageReferencesTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackageReferencesTask.kt new file mode 100644 index 000000000..b58c717e3 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackageReferencesTask.kt @@ -0,0 +1,42 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import org.gradle.api.tasks.SourceTask +import org.gradle.api.tasks.TaskAction +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject + +/** + * @author Akash Yadav + */ +abstract class JavacRenamePackageReferencesTask : SourceTask() { + + @get:Inject + abstract val workerExecutor: WorkerExecutor + + @TaskAction + fun doRenameReferences() { + val workQueue = workerExecutor.noIsolation() + for (file in source.files) { + workQueue.submit(JavacRenamePackgeReferencesWorker::class.java) { + this.file.set(file) + } + } + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackgeReferencesWorker.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackgeReferencesWorker.kt new file mode 100644 index 000000000..5c56a84d4 --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JavacRenamePackgeReferencesWorker.kt @@ -0,0 +1,70 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import org.gradle.api.file.RegularFileProperty +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters + +/** + * @author Akash Yadav + */ +abstract class JavacRenamePackgeReferencesWorker: WorkAction { + + companion object { + + private val unsafeToReplacePackages = arrayOf( + "com.sun", + "jdk.internal", + "sun.tools" + ) + + private val safeToReplacePackages = arrayOf( + "javax.annotation", + "javax.lang", + "javax.tools", + ) + + private val replaceTypes = arrayOf( + "package", + "import", + "import static" + ) + } + + override fun execute() { + val file = parameters.file.get().asFile + var content = file.readText() + + for (pck in unsafeToReplacePackages) { + for (typ in replaceTypes) { + content = content.replace(Regex.fromLiteral("$typ $pck"), "$typ com.itsaky.androidide.$pck") + } + } + + for (pck in safeToReplacePackages) { + content = content.replace(Regex.fromLiteral(pck), "com.itsaky.androidide.$pck") + } + + file.writeText(content) + } + + interface Params: WorkParameters { + val file: RegularFileProperty + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JdkSourceDownloadTask.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JdkSourceDownloadTask.kt new file mode 100644 index 000000000..c35a8e1ed --- /dev/null +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/tasks/javac/JdkSourceDownloadTask.kt @@ -0,0 +1,89 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.plugins.tasks.javac + +import com.itsaky.androidide.plugins.util.DownloadUtils +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +/** + * Task to download JDK source code. + * + * @author Akash Yadav + */ +abstract class JdkSourceDownloadTask : DefaultTask() { + + /** + * The commit hash to download. + */ + @get:Input + abstract val commitHash: Property + + /** + * The SHA-256 hash of the source tarball. + */ + @get:Input + abstract val sourceSHA256: Property + + /** + * The directory to download the source tarbar to. + */ + @get:OutputFile + @get:Optional + abstract val outputFile: RegularFileProperty + + init { + run { + commitHash.convention(DEFAULT_JDK_COMMIT_HASH) + sourceSHA256.convention(DEFAULT_JDK_SOURCE_SHA) + outputFile.convention(project.layout.buildDirectory.dir(name).get().file("${commitHash.get()}.tar.gz")) + } + } + + companion object { + + private const val DEFAULT_JDK_COMMIT_HASH = "3036d3921cae2900aab4610b1e22ee816ac64e2d" + private const val DEFAULT_JDK_SOURCE_SHA = "d6d9aa6465becf1332bdca6d60cbf37f66ac0d2e7755c3197125d452f03ec657" + private const val REPOSITORY_OWNER = "itsaky" + private const val REPOSITORY_NAME = "openjdk-21-android" + private const val JDK_DOWNLOAD_URL = "https://github.com/${REPOSITORY_OWNER}/${REPOSITORY_NAME}/archive/%s.tar.gz" + } + + @TaskAction + fun downloadSource() { + val downloadUrl = JDK_DOWNLOAD_URL.format(commitHash.get()) + val outFile = outputFile.get().asFile + + DownloadUtils.doDownload(outFile, downloadUrl, sourceSHA256.get(), logger) + + if (!outFile.exists() || !outFile.isFile) { + throw GradleException("$outFile does not exist or is not a file.") + } + } + + fun jdkSourceDirName() : String { + return "${REPOSITORY_NAME}-${commitHash.get()}" + } +} \ No newline at end of file diff --git a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/util/DownloadUtils.kt b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/util/DownloadUtils.kt index 7034d6f5a..1ab54cd5f 100644 --- a/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/util/DownloadUtils.kt +++ b/build-logic/ide/src/main/java/com/itsaky/androidide/plugins/util/DownloadUtils.kt @@ -42,7 +42,7 @@ object DownloadUtils { logger: Logger ) { - logger.info("Download $remoteUrl to $file having checksum ${expectedChecksum}...") + logger.lifecycle("Download $remoteUrl to $file having checksum ${expectedChecksum}...") val digest = MessageDigest.getInstance("SHA-256") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ffd8142eb..d655f52ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,6 @@ maven-publish-plugin = "0.25.3" [libraries] # AndroidIDE -androidide-javac = { module = "io.github.itsaky:nb-javac-android", version = "17.0.0.4-SNAPSHOT" } androidide-ts = { module = "com.itsaky.androidide.treesitter:android-tree-sitter", version.ref = "tree-sitter" } androidide-ts-java = { module = "com.itsaky.androidide.treesitter:tree-sitter-java", version.ref = "tree-sitter" } androidide-ts-json = { module = "com.itsaky.androidide.treesitter:tree-sitter-json", version.ref = "tree-sitter" } @@ -144,6 +143,9 @@ kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v nav-safe-args-gradle-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "androidx-navigation" } maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish-plugin" } +# Misc +misc-jackpot30 = { module = "org.apache.netbeans.modules.jackpot30:tool", version = "13.0" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 63d938442..ff1e49a80 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,11 @@ @file:Suppress("UnstableApiUsage") +include(":subprojects:javac-deps") + + +include(":subprojects:javac-jackpot") + + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { diff --git a/subprojects/javac-deps/.gitignore b/subprojects/javac-deps/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/subprojects/javac-deps/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/subprojects/javac-deps/build.gradle.kts b/subprojects/javac-deps/build.gradle.kts new file mode 100644 index 000000000..8f36f26b0 --- /dev/null +++ b/subprojects/javac-deps/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +@Suppress("") +plugins { + id("java-library") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationInvocationHandler.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationInvocationHandler.java new file mode 100644 index 000000000..32da04f06 --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationInvocationHandler.java @@ -0,0 +1,712 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.sun.reflect.annotation; + +import java.io.ObjectInputStream; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.io.Serializable; +import java.util.*; +import java.util.stream.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import sun.reflect.annotation.AnnotationType; + +/** + * InvocationHandler for dynamic proxy implementation of Annotation. + * + * @author Josh Bloch + * @since 1.5 + */ +@SuppressWarnings("Since15") +class AnnotationInvocationHandler implements InvocationHandler, Serializable { + @java.io.Serial + private static final long serialVersionUID = 6182022883658399397L; + private final Class type; + @SuppressWarnings("serial") // Not statically typed as Serializable + private final Map memberValues; + + AnnotationInvocationHandler(Class type, Map memberValues) { + Class[] superInterfaces = type.getInterfaces(); + if (!type.isAnnotation() || + superInterfaces.length != 1 || + superInterfaces[0] != Annotation.class) + throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type: " + + type.getName()); + this.type = type; + this.memberValues = memberValues; + } + + public Object invoke(Object proxy, Method method, Object[] args) { + String member = method.getName(); + int parameterCount = method.getParameterCount(); + + // Handle Object and Annotation methods + if (parameterCount == 1 && member == "equals" && + method.getParameterTypes()[0] == Object.class) { + return equalsImpl(proxy, args[0]); + } + if (parameterCount != 0) { + throw new AssertionError("Too many parameters for an annotation method"); + } + + if (member == "toString") { + return toStringImpl(); + } else if (member == "hashCode") { + return hashCodeImpl(); + } else if (member == "annotationType") { + return type; + } + + // Handle annotation member accessors + Object result = memberValues.get(member); + + if (result == null) + throw new IncompleteAnnotationException(type, member); + + if (result instanceof ExceptionProxy) + throw ((ExceptionProxy) result).generateException(); + + if (result.getClass().isArray() && Array.getLength(result) != 0) + result = cloneArray(result); + + return result; + } + + /** + * This method, which clones its array argument, would not be necessary + * if Cloneable had a public clone method. + */ + private Object cloneArray(Object array) { + Class type = array.getClass(); + + if (type == byte[].class) { + byte[] byteArray = (byte[])array; + return byteArray.clone(); + } + if (type == char[].class) { + char[] charArray = (char[])array; + return charArray.clone(); + } + if (type == double[].class) { + double[] doubleArray = (double[])array; + return doubleArray.clone(); + } + if (type == float[].class) { + float[] floatArray = (float[])array; + return floatArray.clone(); + } + if (type == int[].class) { + int[] intArray = (int[])array; + return intArray.clone(); + } + if (type == long[].class) { + long[] longArray = (long[])array; + return longArray.clone(); + } + if (type == short[].class) { + short[] shortArray = (short[])array; + return shortArray.clone(); + } + if (type == boolean[].class) { + boolean[] booleanArray = (boolean[])array; + return booleanArray.clone(); + } + + Object[] objectArray = (Object[])array; + return objectArray.clone(); + } + + + /** + * Implementation of dynamicProxy.toString() + */ + private String toStringImpl() { + StringBuilder result = new StringBuilder(128); + result.append('@'); + // Guard against null canonical name; shouldn't happen + result.append(Objects.toString(type.getCanonicalName(), + "")); + result.append('('); + boolean firstMember = true; + Set> entries = memberValues.entrySet(); + boolean loneValue = entries.size() == 1; + for (Map.Entry e : entries) { + if (firstMember) + firstMember = false; + else + result.append(", "); + + String key = e.getKey(); + if (!loneValue || !"value".equals(key)) { + result.append(key); + result.append('='); + } + loneValue = false; + result.append(memberValueToString(e.getValue())); + } + result.append(')'); + return result.toString(); + } + + /** + * Translates a member value (in "dynamic proxy return form") into a string. + */ + private static String memberValueToString(Object value) { + Class type = value.getClass(); + if (!type.isArray()) { + // primitive value, string, class, enum const, or annotation + if (type == Class.class) + return toSourceString((Class) value); + else if (type == String.class) + return toSourceString((String) value); + if (type == Character.class) + return toSourceString((char) value); + else if (type == Double.class) + return toSourceString((double) value); + else if (type == Float.class) + return toSourceString((float) value); + else if (type == Long.class) + return toSourceString((long) value); + else if (type == Byte.class) + return toSourceString((byte) value); + else if (value instanceof Enum) + // Predicate above covers enum constants, including + // those with specialized class bodies. + return toSourceString(((Enum) value)); + else + return value.toString(); + } else { + Stream stringStream; + if (type == byte[].class) + stringStream = convert((byte[]) value); + else if (type == char[].class) + stringStream = convert((char[]) value); + else if (type == double[].class) + stringStream = DoubleStream.of((double[]) value) + .mapToObj(AnnotationInvocationHandler::toSourceString); + else if (type == float[].class) + stringStream = convert((float[]) value); + else if (type == int[].class) + stringStream = IntStream.of((int[]) value).mapToObj(String::valueOf); + else if (type == long[].class) { + stringStream = LongStream.of((long[]) value) + .mapToObj(AnnotationInvocationHandler::toSourceString); + } else if (type == short[].class) + stringStream = convert((short[]) value); + else if (type == boolean[].class) + stringStream = convert((boolean[]) value); + else if (type == Class[].class) + stringStream = + Arrays.stream((Class[]) value). + map(AnnotationInvocationHandler::toSourceString); + else if (type == String[].class) + stringStream = + Arrays.stream((String[])value). + map(AnnotationInvocationHandler::toSourceString); + else if (type.getComponentType().isEnum()) + stringStream = + Arrays.stream((Enum[])value). + map(AnnotationInvocationHandler::toSourceString); + else + stringStream = Arrays.stream((Object[])value).map(Objects::toString); + + return stringStreamToString(stringStream); + } + } + + /** + * Translates a Class value to a form suitable for use in the + * string representation of an annotation. + */ + private static String toSourceString(Class clazz) { + // Guard against null canonical name; shouldn't happen + return Objects.toString(clazz.getCanonicalName(), + "") + ".class"; + } + + private static String toSourceString(float f) { + if (Float.isFinite(f)) + return Float.toString(f) + "f" ; + else { + if (Float.isInfinite(f)) { + return (f < 0.0f) ? "-1.0f/0.0f": "1.0f/0.0f"; + } else + return "0.0f/0.0f"; + } + } + + private static String toSourceString(double d) { + if (Double.isFinite(d)) + return Double.toString(d); + else { + if (Double.isInfinite(d)) { + return (d < 0.0f) ? "-1.0/0.0": "1.0/0.0"; + } else + return "0.0/0.0"; + } + } + + private static String toSourceString(char c) { + StringBuilder sb = new StringBuilder(4); + sb.append('\''); + sb.append(quote(c)); + return sb.append('\'') .toString(); + } + + /** + * Escapes a character if it has an escape sequence or is + * non-printable ASCII. Leaves non-ASCII characters alone. + */ + private static String quote(char ch) { + switch (ch) { + case '\b': return "\\b"; + case '\f': return "\\f"; + case '\n': return "\\n"; + case '\r': return "\\r"; + case '\t': return "\\t"; + case '\'': return "\\'"; + case '\"': return "\\\""; + case '\\': return "\\\\"; + default: + return (isPrintableAscii(ch)) + ? String.valueOf(ch) + : String.format("\\u%04x", (int) ch); + } + } + + /** + * Is a character printable ASCII? + */ + private static boolean isPrintableAscii(char ch) { + return ch >= ' ' && ch <= '~'; + } + + private static String toSourceString(byte b) { + return String.format("(byte)0x%02x", b); + } + + private static String toSourceString(long ell) { + return String.valueOf(ell) + "L"; + } + + private static String toSourceString(Enum enumConstant) { + return enumConstant.name(); + } + + /** + * Return a string suitable for use in the string representation + * of an annotation. + */ + private static String toSourceString(String s) { + StringBuilder sb = new StringBuilder(); + sb.append('"'); + for (int i = 0; i < s.length(); i++) { + sb.append(quote(s.charAt(i))); + } + sb.append('"'); + return sb.toString(); + } + + private static Stream convert(byte[] values) { + List list = new ArrayList<>(values.length); + for (byte b : values) + list.add(toSourceString(b)); + return list.stream(); + } + + private static Stream convert(char[] values) { + List list = new ArrayList<>(values.length); + for (char c : values) + list.add(toSourceString(c)); + return list.stream(); + } + + private static Stream convert(float[] values) { + List list = new ArrayList<>(values.length); + for (float f : values) { + list.add(toSourceString(f)); + } + return list.stream(); + } + + private static Stream convert(short[] values) { + List list = new ArrayList<>(values.length); + for (short s : values) + list.add(Short.toString(s)); + return list.stream(); + } + + private static Stream convert(boolean[] values) { + List list = new ArrayList<>(values.length); + for (boolean b : values) + list.add(Boolean.toString(b)); + return list.stream(); + } + + private static String stringStreamToString(Stream stream) { + return stream.collect(Collectors.joining(", ", "{", "}")); + } + + /** + * Implementation of dynamicProxy.equals(Object o) + */ + private Boolean equalsImpl(Object proxy, Object o) { + if (o == proxy) + return true; + + if (!type.isInstance(o)) + return false; + for (Method memberMethod : getMemberMethods()) { + if (memberMethod.isSynthetic()) + continue; + String member = memberMethod.getName(); + Object ourValue = memberValues.get(member); + Object hisValue = null; + AnnotationInvocationHandler hisHandler = asOneOfUs(o); + if (hisHandler != null) { + hisValue = hisHandler.memberValues.get(member); + } else { + try { + hisValue = memberMethod.invoke(o); + } catch (InvocationTargetException e) { + return false; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + if (!memberValueEquals(ourValue, hisValue)) + return false; + } + return true; + } + + /** + * Returns an object's invocation handler if that object is a dynamic + * proxy with a handler of type AnnotationInvocationHandler. + * Returns null otherwise. + */ + private AnnotationInvocationHandler asOneOfUs(Object o) { + if (Proxy.isProxyClass(o.getClass())) { + InvocationHandler handler = Proxy.getInvocationHandler(o); + if (handler instanceof AnnotationInvocationHandler) + return ((AnnotationInvocationHandler) handler); + } + return null; + } + + /** + * Returns true iff the two member values in "dynamic proxy return form" + * are equal using the appropriate equality function depending on the + * member type. The two values will be of the same type unless one of + * the containing annotations is ill-formed. If one of the containing + * annotations is ill-formed, this method will return false unless the + * two members are identical object references. + */ + private static boolean memberValueEquals(Object v1, Object v2) { + Class type = v1.getClass(); + + // Check for primitive, string, class, enum const, annotation, + // or ExceptionProxy + if (!type.isArray()) + return v1.equals(v2); + + // Check for array of string, class, enum const, annotation, + // or ExceptionProxy + if (v1 instanceof Object[] && v2 instanceof Object[]) + return Arrays.equals((Object[]) v1, (Object[]) v2); + + // Check for ill formed annotation(s) + if (v2.getClass() != type) + return false; + + // Deal with array of primitives + if (type == byte[].class) + return Arrays.equals((byte[]) v1, (byte[]) v2); + if (type == char[].class) + return Arrays.equals((char[]) v1, (char[]) v2); + if (type == double[].class) + return Arrays.equals((double[]) v1, (double[]) v2); + if (type == float[].class) + return Arrays.equals((float[]) v1, (float[]) v2); + if (type == int[].class) + return Arrays.equals((int[]) v1, (int[]) v2); + if (type == long[].class) + return Arrays.equals((long[]) v1, (long[]) v2); + if (type == short[].class) + return Arrays.equals((short[]) v1, (short[]) v2); + assert type == boolean[].class; + return Arrays.equals((boolean[]) v1, (boolean[]) v2); + } + + /** + * Returns the member methods for our annotation type. These are + * obtained lazily and cached, as they're expensive to obtain + * and we only need them if our equals method is invoked (which should + * be rare). + */ + private Method[] getMemberMethods() { + Method[] value = memberMethods; + if (value == null) { + value = computeMemberMethods(); + memberMethods = value; + } + return value; + } + + @SuppressWarnings("removal") + private Method[] computeMemberMethods() { + return AccessController.doPrivileged( + new PrivilegedAction() { + public Method[] run() { + final Method[] methods = type.getDeclaredMethods(); + validateAnnotationMethods(methods); + AccessibleObject.setAccessible(methods, true); + return methods; + }}); + } + + private transient volatile Method[] memberMethods; + + /** + * Validates that a method is structurally appropriate for an + * annotation type. As of Java SE 8, annotation types cannot + * contain static methods and the declared methods of an + * annotation type must take zero arguments and there are + * restrictions on the return type. + */ + private void validateAnnotationMethods(Method[] memberMethods) { + /* + * Specification citations below are from JLS + * 9.6.1. Annotation Type Elements + */ + boolean valid = true; + Method currentMethod = null; + for(Method method : memberMethods) { + currentMethod = method; + int modifiers = method.getModifiers(); + // Skip over methods that may be a static initializer or + // similar construct. A static initializer may be used for + // purposes such as initializing a lambda stored in an + // interface field. + if (method.isSynthetic() && + (modifiers & (Modifier.STATIC | Modifier.PRIVATE)) != 0 && + method.getParameterCount() == 0) { + continue; + } + + /* + * "By virtue of the AnnotationTypeElementDeclaration + * production, a method declaration in an annotation type + * declaration cannot have formal parameters, type + * parameters, or a throws clause. + * + * "By virtue of the AnnotationTypeElementModifier + * production, a method declaration in an annotation type + * declaration cannot be default or static." + */ + if (modifiers != (Modifier.PUBLIC | Modifier.ABSTRACT) || + method.isDefault() || + method.getParameterCount() != 0 || + method.getExceptionTypes().length != 0) { + valid = false; + break; + } + + /* + * "It is a compile-time error if the return type of a + * method declared in an annotation type is not one of the + * following: a primitive type, String, Class, any + * parameterized invocation of Class, an enum type + * (section 8.9), an annotation type, or an array type + * (chapter 10) whose element type is one of the preceding + * types." + */ + Class returnType = method.getReturnType(); + if (returnType.isArray()) { + returnType = returnType.getComponentType(); + if (returnType.isArray()) { // Only single dimensional arrays + valid = false; + break; + } + } + + if (!((returnType.isPrimitive() && returnType != void.class) || + returnType == String.class || + returnType == Class.class || + returnType.isEnum() || + returnType.isAnnotation())) { + valid = false; + break; + } + + /* + * "It is a compile-time error if any method declared in an + * annotation type has a signature that is + * override-equivalent to that of any public or protected + * method declared in class Object or in the interface + * java.lang.annotation.Annotation." + * + * The methods in Object or Annotation meeting the other + * criteria (no arguments, constrained return type, etc.) + * above are: + * + * String toString() + * int hashCode() + * Class annotationType() + */ + String methodName = method.getName(); + if ((methodName.equals("toString") && returnType == String.class) || + (methodName.equals("hashCode") && returnType == int.class) || + (methodName.equals("annotationType") && returnType == Class.class)) { + valid = false; + break; + } + } + if (valid) + return; + else + throw new AnnotationFormatError("Malformed method on an annotation type: " + + currentMethod.toString()); + } + + /** + * Implementation of dynamicProxy.hashCode() + */ + private int hashCodeImpl() { + int result = 0; + for (Map.Entry e : memberValues.entrySet()) { + result += (127 * e.getKey().hashCode()) ^ + memberValueHashCode(e.getValue()); + } + return result; + } + + /** + * Computes hashCode of a member value (in "dynamic proxy return form") + */ + private static int memberValueHashCode(Object value) { + Class type = value.getClass(); + if (!type.isArray()) // primitive, string, class, enum const, + // or annotation + return value.hashCode(); + + if (type == byte[].class) + return Arrays.hashCode((byte[]) value); + if (type == char[].class) + return Arrays.hashCode((char[]) value); + if (type == double[].class) + return Arrays.hashCode((double[]) value); + if (type == float[].class) + return Arrays.hashCode((float[]) value); + if (type == int[].class) + return Arrays.hashCode((int[]) value); + if (type == long[].class) + return Arrays.hashCode((long[]) value); + if (type == short[].class) + return Arrays.hashCode((short[]) value); + if (type == boolean[].class) + return Arrays.hashCode((boolean[]) value); + return Arrays.hashCode((Object[]) value); + } + + @java.io.Serial + private void readObject(ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = s.readFields(); + + @SuppressWarnings("unchecked") + Class t = (Class)fields.get("type", null); + @SuppressWarnings("unchecked") + Map streamVals = (Map)fields.get("memberValues", null); + + // Check to make sure that types have not evolved incompatibly + + AnnotationType annotationType = null; + try { + annotationType = AnnotationType.getInstance(t); + } catch(IllegalArgumentException e) { + // Class is no longer an annotation type; time to punch out + throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream"); + } + + Map> memberTypes = annotationType.memberTypes(); + // consistent with runtime Map type + Map mv = new LinkedHashMap<>(); + + // If there are annotation members without values, that + // situation is handled by the invoke method. + for (Map.Entry memberValue : streamVals.entrySet()) { + String name = memberValue.getKey(); + Object value = null; + Class memberType = memberTypes.get(name); + if (memberType != null) { // i.e. member still exists + value = memberValue.getValue(); + if (!(memberType.isInstance(value) || + value instanceof ExceptionProxy)) { + value = new AnnotationTypeMismatchExceptionProxy( + toIdentityString(value)) + .setMember(annotationType.members().get(name)); + } + } + mv.put(name, value); + } + + UnsafeAccessor.setType(this, t); + UnsafeAccessor.setMemberValues(this, mv); + } + + public static String toIdentityString(Object o) { + Objects.requireNonNull(o); + return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o)); + } + + /** + * Gets an unmodifiable view on the member values. + */ + Map memberValues() { + return Collections.unmodifiableMap(memberValues); + } + + private static class UnsafeAccessor { + private static final jdk.internal.misc.Unsafe unsafe + = jdk.internal.misc.Unsafe.getUnsafe(); + private static final long typeOffset = unsafe.objectFieldOffset + (AnnotationInvocationHandler.class, "type"); + private static final long memberValuesOffset = unsafe.objectFieldOffset + (AnnotationInvocationHandler.class, "memberValues"); + + static void setType(AnnotationInvocationHandler o, + Class type) { + unsafe.putReference(o, typeOffset, type); + } + + static void setMemberValues(AnnotationInvocationHandler o, + Map memberValues) { + unsafe.putReference(o, memberValuesOffset, memberValues); + } + } +} diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationParser.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationParser.java new file mode 100644 index 000000000..4b588181d --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationParser.java @@ -0,0 +1,872 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.sun.reflect.annotation; + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.function.Supplier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import jdk.internal.reflect.ConstantPool; + +import sun.reflect.annotation.AnnotationType; +import sun.reflect.annotation.EnumConstantNotPresentExceptionProxy; +import sun.reflect.annotation.ExceptionProxy; +import sun.reflect.annotation.TypeNotPresentExceptionProxy; +import sun.reflect.generics.parser.SignatureParser; +import sun.reflect.generics.tree.TypeSignature; +import sun.reflect.generics.factory.GenericsFactory; +import sun.reflect.generics.factory.CoreReflectionFactory; +import sun.reflect.generics.visitor.Reifier; +import sun.reflect.generics.scope.ClassScope; + +/** + * Parser for Java programming language annotations. Translates + * annotation byte streams emitted by compiler into annotation objects. + * + * @author Josh Bloch + * @since 1.5 + */ +public class AnnotationParser { + /** + * Parses the annotations described by the specified byte array. + * resolving constant references in the specified constant pool. + * The array must contain an array of annotations as described + * in the RuntimeVisibleAnnotations_attribute: + * + * u2 num_annotations; + * annotation annotations[num_annotations]; + * + * @throws AnnotationFormatError if an annotation is found to be + * malformed. + */ + public static Map, Annotation> parseAnnotations( + byte[] rawAnnotations, + ConstantPool constPool, + Class container) { + if (rawAnnotations == null) + return Collections.emptyMap(); + + try { + return parseAnnotations2(rawAnnotations, constPool, container, null); + } catch(BufferUnderflowException e) { + throw new AnnotationFormatError("Unexpected end of annotations."); + } catch(IllegalArgumentException e) { + // Type mismatch in constant pool + throw new AnnotationFormatError(e); + } + } + + /** + * Like {@link #parseAnnotations(byte[], sun.reflect.ConstantPool, Class)} + * with an additional parameter {@code selectAnnotationClasses} which selects the + * annotation types to parse (other than selected are quickly skipped).

+ * This method is used to parse select meta annotations in the construction + * phase of {@link AnnotationType} instances to prevent infinite recursion. + * + * @param selectAnnotationClasses an array of annotation types to select when parsing + */ + @SafeVarargs + @SuppressWarnings("varargs") // selectAnnotationClasses is used safely + public static Map, Annotation> parseSelectAnnotations( + byte[] rawAnnotations, + ConstantPool constPool, + Class container, + Class ... selectAnnotationClasses) { + if (rawAnnotations == null) + return Collections.emptyMap(); + + try { + return parseAnnotations2(rawAnnotations, constPool, container, selectAnnotationClasses); + } catch(BufferUnderflowException e) { + throw new AnnotationFormatError("Unexpected end of annotations."); + } catch(IllegalArgumentException e) { + // Type mismatch in constant pool + throw new AnnotationFormatError(e); + } + } + + private static Map, Annotation> parseAnnotations2( + byte[] rawAnnotations, + ConstantPool constPool, + Class container, + Class[] selectAnnotationClasses) { + Map, Annotation> result = + new LinkedHashMap, Annotation>(); + ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); + int numAnnotations = buf.getShort() & 0xFFFF; + for (int i = 0; i < numAnnotations; i++) { + Annotation a = parseAnnotation2(buf, constPool, container, false, selectAnnotationClasses); + if (a != null) { + Class klass = a.annotationType(); + if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME && + result.put(klass, a) != null) { + throw new AnnotationFormatError( + "Duplicate annotation for class: "+klass+": " + a); + } + } + } + return result; + } + + /** + * Parses the parameter annotations described by the specified byte array. + * resolving constant references in the specified constant pool. + * The array must contain an array of annotations as described + * in the RuntimeVisibleParameterAnnotations_attribute: + * + * u1 num_parameters; + * { + * u2 num_annotations; + * annotation annotations[num_annotations]; + * } parameter_annotations[num_parameters]; + * + * Unlike parseAnnotations, rawAnnotations must not be null! + * A null value must be handled by the caller. This is so because + * we cannot determine the number of parameters if rawAnnotations + * is null. Also, the caller should check that the number + * of parameters indicated by the return value of this method + * matches the actual number of method parameters. A mismatch + * indicates that an AnnotationFormatError should be thrown. + * + * @throws AnnotationFormatError if an annotation is found to be + * malformed. + */ + public static Annotation[][] parseParameterAnnotations( + byte[] rawAnnotations, + ConstantPool constPool, + Class container) { + try { + return parseParameterAnnotations2(rawAnnotations, constPool, container); + } catch(BufferUnderflowException e) { + throw new AnnotationFormatError( + "Unexpected end of parameter annotations."); + } catch(IllegalArgumentException e) { + // Type mismatch in constant pool + throw new AnnotationFormatError(e); + } + } + + private static Annotation[][] parseParameterAnnotations2( + byte[] rawAnnotations, + ConstantPool constPool, + Class container) { + ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); + int numParameters = buf.get() & 0xFF; + Annotation[][] result = new Annotation[numParameters][]; + + for (int i = 0; i < numParameters; i++) { + int numAnnotations = buf.getShort() & 0xFFFF; + List annotations = + new ArrayList(numAnnotations); + for (int j = 0; j < numAnnotations; j++) { + Annotation a = parseAnnotation(buf, constPool, container, false); + if (a != null) { + AnnotationType type = AnnotationType.getInstance( + a.annotationType()); + if (type.retention() == RetentionPolicy.RUNTIME) + annotations.add(a); + } + } + result[i] = annotations.toArray(EMPTY_ANNOTATIONS_ARRAY); + } + return result; + } + + private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = + new Annotation[0]; + + /** + * Parses the annotation at the current position in the specified + * byte buffer, resolving constant references in the specified constant + * pool. The cursor of the byte buffer must point to an "annotation + * structure" as described in the RuntimeVisibleAnnotations_attribute: + * + * annotation { + * u2 type_index; + * u2 num_member_value_pairs; + * { u2 member_name_index; + * member_value value; + * } member_value_pairs[num_member_value_pairs]; + * } + * } + * + * Returns the annotation, or null if the annotation's type cannot + * be found by the VM, or is not a valid annotation type. + * + * @param exceptionOnMissingAnnotationClass if true, throw + * TypeNotPresentException if a referenced annotation type is not + * available at runtime + */ + static Annotation parseAnnotation(ByteBuffer buf, + ConstantPool constPool, + Class container, + boolean exceptionOnMissingAnnotationClass) { + return parseAnnotation2(buf, constPool, container, exceptionOnMissingAnnotationClass, null); + } + + @SuppressWarnings("unchecked") + private static Annotation parseAnnotation2(ByteBuffer buf, + ConstantPool constPool, + Class container, + boolean exceptionOnMissingAnnotationClass, + Class[] selectAnnotationClasses) { + int typeIndex = buf.getShort() & 0xFFFF; + Class annotationClass = null; + String sig = "[unknown]"; + try { + sig = constPool.getUTF8At(typeIndex); + annotationClass = (Class)parseSig(sig, container); + } catch (NoClassDefFoundError e) { + if (exceptionOnMissingAnnotationClass) + // note: at this point sig is "[unknown]" or VM-style + // name instead of a binary name + throw new TypeNotPresentException(sig, e); + skipAnnotation(buf, false); + return null; + } + catch (TypeNotPresentException e) { + if (exceptionOnMissingAnnotationClass) + throw e; + skipAnnotation(buf, false); + return null; + } + if (selectAnnotationClasses != null && !contains(selectAnnotationClasses, annotationClass)) { + skipAnnotation(buf, false); + return null; + } + AnnotationType type = null; + try { + type = AnnotationType.getInstance(annotationClass); + } catch (IllegalArgumentException e) { + skipAnnotation(buf, false); + return null; + } + + Map> memberTypes = type.memberTypes(); + Map memberValues = + new LinkedHashMap(type.memberDefaults()); + + int numMembers = buf.getShort() & 0xFFFF; + for (int i = 0; i < numMembers; i++) { + int memberNameIndex = buf.getShort() & 0xFFFF; + String memberName = constPool.getUTF8At(memberNameIndex); + Class memberType = memberTypes.get(memberName); + + if (memberType == null) { + // Member is no longer present in annotation type; ignore it + skipMemberValue(buf); + } else { + Object value = parseMemberValue(memberType, buf, constPool, container); + if (value instanceof AnnotationTypeMismatchExceptionProxy) + ((AnnotationTypeMismatchExceptionProxy) value).setMember(type.members().get(memberName)); + memberValues.put(memberName, value); + } + } + return annotationForMap(annotationClass, memberValues); + } + + /** + * Returns an annotation of the given type backed by the given + * member {@literal ->} value map. + */ + @SuppressWarnings("removal") + public static Annotation annotationForMap(final Class type, + final Map memberValues) + { + return AccessController.doPrivileged(new PrivilegedAction() { + public Annotation run() { + return (Annotation) Proxy.newProxyInstance( + type.getClassLoader(), new Class[] { type }, + new AnnotationInvocationHandler(type, memberValues)); + }}); + } + + /** + * Parses the annotation member value at the current position in the + * specified byte buffer, resolving constant references in the specified + * constant pool. The cursor of the byte buffer must point to a + * "member_value structure" as described in the + * RuntimeVisibleAnnotations_attribute: + * + * member_value { + * u1 tag; + * union { + * u2 const_value_index; + * { + * u2 type_name_index; + * u2 const_name_index; + * } enum_const_value; + * u2 class_info_index; + * annotation annotation_value; + * { + * u2 num_values; + * member_value values[num_values]; + * } array_value; + * } value; + * } + * + * The member must be of the indicated type. If it is not, this + * method returns an AnnotationTypeMismatchExceptionProxy. + */ + @SuppressWarnings("unchecked") + public static Object parseMemberValue(Class memberType, + ByteBuffer buf, + ConstantPool constPool, + Class container) { + // Note that VMSupport.encodeAnnotation (used by JVMCI) may need to + // be updated if new annotation member types are added. + Object result = null; + int tag = buf.get(); + switch(tag) { + case 'e': + return parseEnumValue((Class>)memberType, buf, constPool, container); + case 'c': + result = parseClassValue(buf, constPool, container); + break; + case '@': + result = parseAnnotation(buf, constPool, container, true); + break; + case '[': + return parseArray(memberType, buf, constPool, container); + default: + result = parseConst(tag, buf, constPool); + } + + if (result == null) { + result = new AnnotationTypeMismatchExceptionProxy( + Proxy.isProxyClass(memberType) + ? memberType.getInterfaces()[0].getName() + : memberType.getName()); + } else if (!(result instanceof ExceptionProxy) && + !memberType.isInstance(result)) { + if (result instanceof Annotation) { + result = new AnnotationTypeMismatchExceptionProxy( + result.toString()); + } else { + result = new AnnotationTypeMismatchExceptionProxy( + result.getClass().getName() + "[" + result + "]"); + } + } + return result; + } + + /** + * Parses the primitive or String annotation member value indicated by + * the specified tag byte at the current position in the specified byte + * buffer, resolving constant reference in the specified constant pool. + * The cursor of the byte buffer must point to an annotation member value + * of the type indicated by the specified tag, as described in the + * RuntimeVisibleAnnotations_attribute: + * + * u2 const_value_index; + */ + private static Object parseConst(int tag, + ByteBuffer buf, ConstantPool constPool) { + int constIndex = buf.getShort() & 0xFFFF; + switch(tag) { + case 'B': + return Byte.valueOf((byte) constPool.getIntAt(constIndex)); + case 'C': + return Character.valueOf((char) constPool.getIntAt(constIndex)); + case 'D': + return Double.valueOf(constPool.getDoubleAt(constIndex)); + case 'F': + return Float.valueOf(constPool.getFloatAt(constIndex)); + case 'I': + return Integer.valueOf(constPool.getIntAt(constIndex)); + case 'J': + return Long.valueOf(constPool.getLongAt(constIndex)); + case 'S': + return Short.valueOf((short) constPool.getIntAt(constIndex)); + case 'Z': + return Boolean.valueOf(constPool.getIntAt(constIndex) != 0); + case 's': + return constPool.getUTF8At(constIndex); + default: + throw new AnnotationFormatError( + "Invalid member-value tag in annotation: " + tag); + } + } + + /** + * Parses the Class member value at the current position in the + * specified byte buffer, resolving constant references in the specified + * constant pool. The cursor of the byte buffer must point to a "class + * info index" as described in the RuntimeVisibleAnnotations_attribute: + * + * u2 class_info_index; + */ + private static Object parseClassValue(ByteBuffer buf, + ConstantPool constPool, + Class container) { + int classIndex = buf.getShort() & 0xFFFF; + try { + String sig = constPool.getUTF8At(classIndex); + return parseSig(sig, container); + } catch (NoClassDefFoundError e) { + return new TypeNotPresentExceptionProxy("[unknown]", e); + } catch (TypeNotPresentException e) { + return new TypeNotPresentExceptionProxy(e.typeName(), e.getCause()); + } + } + + private static Class parseSig(String sig, Class container) { + if (sig.equals("V")) return void.class; + SignatureParser parser = SignatureParser.make(); + TypeSignature typeSig = parser.parseTypeSig(sig); + GenericsFactory factory = CoreReflectionFactory.make(container, ClassScope.make(container)); + Reifier reify = Reifier.make(factory); + typeSig.accept(reify); + Type result = reify.getResult(); + return toClass(result); + } + static Class toClass(Type o) { + if (o instanceof GenericArrayType) + return Array.newInstance(toClass(((GenericArrayType) o).getGenericComponentType()), 0) + .getClass(); + return (Class)o; + } + + /** + * Parses the enum constant member value at the current position in the + * specified byte buffer, resolving constant references in the specified + * constant pool. The cursor of the byte buffer must point to a + * "enum_const_value structure" as described in the + * RuntimeVisibleAnnotations_attribute: + * + * { + * u2 type_name_index; + * u2 const_name_index; + * } enum_const_value; + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private static Object parseEnumValue(Class enumType, ByteBuffer buf, + ConstantPool constPool, + Class container) { + int typeNameIndex = buf.getShort() & 0xFFFF; + String typeName = constPool.getUTF8At(typeNameIndex); + int constNameIndex = buf.getShort() & 0xFFFF; + String constName = constPool.getUTF8At(constNameIndex); + if (!enumType.isEnum() || enumType != parseSig(typeName, container)) { + return new AnnotationTypeMismatchExceptionProxy( + typeName.substring(1, typeName.length() - 1).replace('/', '.') + "." + constName); + } + + try { + return Enum.valueOf(enumType, constName); + } catch(IllegalArgumentException e) { + return new EnumConstantNotPresentExceptionProxy( + (Class>)enumType, constName); + } + } + + /** + * Parses the array value at the current position in the specified byte + * buffer, resolving constant references in the specified constant pool. + * The cursor of the byte buffer must point to an array value struct + * as specified in the RuntimeVisibleAnnotations_attribute: + * + * { + * u2 num_values; + * member_value values[num_values]; + * } array_value; + * + * If the array values do not match arrayType, an + * AnnotationTypeMismatchExceptionProxy will be returned. + */ + @SuppressWarnings("unchecked") + private static Object parseArray(Class arrayType, + ByteBuffer buf, + ConstantPool constPool, + Class container) { + int length = buf.getShort() & 0xFFFF; // Number of array components + if (!arrayType.isArray()) { + return parseUnknownArray(length, buf); + } + Class componentType = arrayType.getComponentType(); + + if (componentType == byte.class) { + return parseByteArray(length, buf, constPool); + } else if (componentType == char.class) { + return parseCharArray(length, buf, constPool); + } else if (componentType == double.class) { + return parseDoubleArray(length, buf, constPool); + } else if (componentType == float.class) { + return parseFloatArray(length, buf, constPool); + } else if (componentType == int.class) { + return parseIntArray(length, buf, constPool); + } else if (componentType == long.class) { + return parseLongArray(length, buf, constPool); + } else if (componentType == short.class) { + return parseShortArray(length, buf, constPool); + } else if (componentType == boolean.class) { + return parseBooleanArray(length, buf, constPool); + } else if (componentType == String.class) { + return parseStringArray(length, buf, constPool); + } else if (componentType == Class.class) { + return parseClassArray(length, buf, constPool, container); + } else if (componentType.isEnum()) { + return parseEnumArray(length, (Class>)componentType, buf, + constPool, container); + } else if (componentType.isAnnotation()) { + return parseAnnotationArray(length, (Class )componentType, buf, + constPool, container); + } else { + return parseUnknownArray(length, buf); + } + } + + private static Object parseByteArray(int length, + ByteBuffer buf, ConstantPool constPool) { + byte[] result = new byte[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'B') { + int index = buf.getShort() & 0xFFFF; + result[i] = (byte) constPool.getIntAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseCharArray(int length, + ByteBuffer buf, ConstantPool constPool) { + char[] result = new char[length]; + boolean typeMismatch = false; + byte tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'C') { + int index = buf.getShort() & 0xFFFF; + result[i] = (char) constPool.getIntAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseDoubleArray(int length, + ByteBuffer buf, ConstantPool constPool) { + double[] result = new double[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'D') { + int index = buf.getShort() & 0xFFFF; + result[i] = constPool.getDoubleAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseFloatArray(int length, + ByteBuffer buf, ConstantPool constPool) { + float[] result = new float[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'F') { + int index = buf.getShort() & 0xFFFF; + result[i] = constPool.getFloatAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseIntArray(int length, + ByteBuffer buf, ConstantPool constPool) { + int[] result = new int[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'I') { + int index = buf.getShort() & 0xFFFF; + result[i] = constPool.getIntAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseLongArray(int length, + ByteBuffer buf, ConstantPool constPool) { + long[] result = new long[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'J') { + int index = buf.getShort() & 0xFFFF; + result[i] = constPool.getLongAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseShortArray(int length, + ByteBuffer buf, ConstantPool constPool) { + short[] result = new short[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'S') { + int index = buf.getShort() & 0xFFFF; + result[i] = (short) constPool.getIntAt(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseBooleanArray(int length, + ByteBuffer buf, ConstantPool constPool) { + boolean[] result = new boolean[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'Z') { + int index = buf.getShort() & 0xFFFF; + result[i] = (constPool.getIntAt(index) != 0); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseStringArray(int length, + ByteBuffer buf, ConstantPool constPool) { + String[] result = new String[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 's') { + int index = buf.getShort() & 0xFFFF; + result[i] = constPool.getUTF8At(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + private static Object parseClassArray(int length, + ByteBuffer buf, + ConstantPool constPool, + Class container) { + return parseArrayElements(new Class[length], + buf, 'c', () -> parseClassValue(buf, constPool, container)); + } + + private static Object parseEnumArray(int length, Class> enumType, + ByteBuffer buf, + ConstantPool constPool, + Class container) { + return parseArrayElements((Object[]) Array.newInstance(enumType, length), + buf, 'e', () -> parseEnumValue(enumType, buf, constPool, container)); + } + + private static Object parseAnnotationArray(int length, + Class annotationType, + ByteBuffer buf, + ConstantPool constPool, + Class container) { + return parseArrayElements((Object[]) Array.newInstance(annotationType, length), + buf, '@', () -> parseAnnotation(buf, constPool, container, true)); + } + + private static Object parseArrayElements(Object[] result, + ByteBuffer buf, + int expectedTag, + Supplier parseElement) { + Object exceptionProxy = null; + for (int i = 0; i < result.length; i++) { + int tag = buf.get(); + if (tag == expectedTag) { + Object value = parseElement.get(); + if (value instanceof ExceptionProxy) { + if (exceptionProxy == null) + exceptionProxy = ((ExceptionProxy) value); + } else { + result[i] = value; + } + } else { + skipMemberValue(tag, buf); + if (exceptionProxy == null) exceptionProxy = exceptionProxy(tag); + } + } + return (exceptionProxy != null) ? exceptionProxy : result; + } + + private static Object parseUnknownArray(int length, + ByteBuffer buf) { + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + skipMemberValue(tag, buf); + } + + return exceptionProxy(tag); + } + + /** + * Returns an appropriate exception proxy for a mismatching array + * annotation where the erroneous array has the specified tag. + */ + private static ExceptionProxy exceptionProxy(int tag) { + return new AnnotationTypeMismatchExceptionProxy( + "Array with component tag: " + (tag == 0 ? "0" : (char) tag)); + } + + /** + * Skips the annotation at the current position in the specified + * byte buffer. The cursor of the byte buffer must point to + * an "annotation structure" OR two bytes into an annotation + * structure (i.e., after the type index). + * + * @param complete true if the byte buffer points to the beginning + * of an annotation structure (rather than two bytes in). + */ + private static void skipAnnotation(ByteBuffer buf, boolean complete) { + if (complete) + buf.getShort(); // Skip type index + int numMembers = buf.getShort() & 0xFFFF; + for (int i = 0; i < numMembers; i++) { + buf.getShort(); // Skip memberNameIndex + skipMemberValue(buf); + } + } + + /** + * Skips the annotation member value at the current position in the + * specified byte buffer. The cursor of the byte buffer must point to a + * "member_value structure." + */ + private static void skipMemberValue(ByteBuffer buf) { + int tag = buf.get(); + skipMemberValue(tag, buf); + } + + /** + * Skips the annotation member value at the current position in the + * specified byte buffer. The cursor of the byte buffer must point + * immediately after the tag in a "member_value structure." + */ + private static void skipMemberValue(int tag, ByteBuffer buf) { + switch(tag) { + case 'e': // Enum value + buf.getInt(); // (Two shorts, actually.) + break; + case '@': + skipAnnotation(buf, true); + break; + case '[': + skipArray(buf); + break; + default: + // Class, primitive, or String + buf.getShort(); + } + } + + /** + * Skips the array value at the current position in the specified byte + * buffer. The cursor of the byte buffer must point to an array value + * struct. + */ + private static void skipArray(ByteBuffer buf) { + int length = buf.getShort() & 0xFFFF; + for (int i = 0; i < length; i++) + skipMemberValue(buf); + } + + /** + * Searches for given {@code element} in given {@code array} by identity. + * Returns {@code true} if found {@code false} if not. + */ + private static boolean contains(Object[] array, Object element) { + for (Object e : array) + if (e == element) + return true; + return false; + } + + /* + * This method converts the annotation map returned by the parseAnnotations() + * method to an array. It is called by Field.getDeclaredAnnotations(), + * Method.getDeclaredAnnotations(), and Constructor.getDeclaredAnnotations(). + * This avoids the reflection classes to load the Annotation class until + * it is needed. + */ + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + public static Annotation[] toArray(Map, Annotation> annotations) { + return annotations.values().toArray(EMPTY_ANNOTATION_ARRAY); + } + + static Annotation[] getEmptyAnnotationArray() { return EMPTY_ANNOTATION_ARRAY; } +} diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeAccess.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeAccess.java new file mode 100644 index 000000000..a74724ac6 --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeAccess.java @@ -0,0 +1,33 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.sun.reflect.annotation; + +import sun.reflect.annotation.AnnotationType; + +/** + * @author Akash Yadav + */ +public class AnnotationTypeAccess { + private AnnotationTypeAccess() { + throw new UnsupportedOperationException(); + } + + public static Class invocationHandlerReturnType(Class cls) { + return AnnotationType.invocationHandlerReturnType(cls); + } +} diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java new file mode 100644 index 000000000..b8fc9d62b --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.sun.reflect.annotation; +import java.lang.annotation.*; +import java.lang.reflect.Method; +import sun.reflect.annotation.ExceptionProxy; + +/** + * ExceptionProxy for AnnotationTypeMismatchException. + * + * @author Josh Bloch + * @since 1.5 + */ +@SuppressWarnings("Since15") +class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy { + @java.io.Serial + private static final long serialVersionUID = 7844069490309503934L; + @SuppressWarnings("serial") // Not statically typed as Serializable + private Method member; // Would be more robust to null-out in a writeObject method. + private final String foundType; + + /** + * It turns out to be convenient to construct these proxies in + * two stages. Since this is a private implementation class, we + * permit ourselves this liberty even though it's normally a very + * bad idea. + */ + AnnotationTypeMismatchExceptionProxy(String foundType) { + this.foundType = foundType; + } + + AnnotationTypeMismatchExceptionProxy setMember(Method member) { + this.member = member; + return this; + } + + protected RuntimeException generateException() { + return new AnnotationTypeMismatchException(member, foundType); + } + + @Override + public String toString() { + return "/* Warning type mismatch! \"" + foundType + "\" */" ; + } +} diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java new file mode 100644 index 000000000..4e2a4954b --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.sun.reflect.annotation; + +import sun.reflect.annotation.ExceptionProxy; + +/** + * ExceptionProxy for EnumConstantNotPresentException. + * + * @author Josh Bloch + * @since 1.5 + */ +public class EnumConstantNotPresentExceptionProxy extends ExceptionProxy { + @java.io.Serial + private static final long serialVersionUID = -604662101303187330L; + final Class> enumType; + final String constName; + + public EnumConstantNotPresentExceptionProxy(Class> enumType, + String constName) { + this.enumType = enumType; + this.constName = constName; + } + + protected RuntimeException generateException() { + return new EnumConstantNotPresentException(enumType, constName); + } + + @Override + public String toString() { + return constName + " /* Warning: constant not present! */"; + } +} diff --git a/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/ExceptionProxy.java b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/ExceptionProxy.java new file mode 100644 index 000000000..e1f1deef3 --- /dev/null +++ b/subprojects/javac-deps/src/main/java/com/itsaky/androidide/sun/reflect/annotation/ExceptionProxy.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.sun.reflect.annotation; + +/** + * An instance of this class is stored in an AnnotationInvocationHandler's + * "memberValues" map in lieu of a value for an annotation member that + * cannot be returned due to some exceptional condition (typically some + * form of illegal evolution of the annotation class). The ExceptionProxy + * instance describes the exception that the dynamic proxy should throw if + * it is queried for this member. + * + * @author Josh Bloch + * @since 1.5 + */ +@SuppressWarnings("Since15") +public abstract class ExceptionProxy implements java.io.Serializable { + @java.io.Serial + private static final long serialVersionUID = 7241930048386631401L; + protected abstract RuntimeException generateException(); +} diff --git a/subprojects/javac-jackpot/.gitignore b/subprojects/javac-jackpot/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/subprojects/javac-jackpot/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/subprojects/javac-jackpot/build.gradle.kts b/subprojects/javac-jackpot/build.gradle.kts new file mode 100644 index 000000000..b0ef67c8e --- /dev/null +++ b/subprojects/javac-jackpot/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +import com.itsaky.androidide.plugins.JavacJackpotPlugin + +@Suppress("JavaPluginLanguageLevel") +plugins { + id("java-library") +} + +apply { + plugin(JavacJackpotPlugin::class.java) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +dependencies { + api(projects.subprojects.javacDeps) +} \ No newline at end of file diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/JmodFileWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/JmodFileWrapper.java new file mode 100644 index 000000000..b2dfd4006 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/JmodFileWrapper.java @@ -0,0 +1,53 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.io.IOException; +import java.nio.file.Path; + +public class JmodFileWrapper { + + public static void checkMagic(Path file) throws IOException { + //TODO: perform validation + } + +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ModuleWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ModuleWrapper.java new file mode 100644 index 000000000..562416596 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ModuleWrapper.java @@ -0,0 +1,148 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.itsaky.androidide.javac.wrappers; + +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Set; + +public class ModuleWrapper { + private final Class clazz; + + private ModuleWrapper(Class c) { + this.clazz = c; + } + + public static ModuleWrapper getModule(Class clazz) { + return new ModuleWrapper(clazz); + } + + public static ModuleWrapper getUnnamedModule(ClassLoader loader) { + return new ModuleWrapper(null); + } + + public String getName() { + switch (clazz.getName()) { + case "jdk.javadoc.internal.api.JavadocTool": return "jdk.javadoc"; + case "com.sun.tools.javac.api.JavacTool": return "jdk.compiler"; + default: return "jdk.compiler"; //XXX + } + } + + public boolean isNamed() { + return false; + } + + public void addExports(String pack, ModuleWrapper to) { + } + + public void addUses(Class service) { + if (this.clazz != null) { + ensureUses(this.clazz, service); + } + ensureUses(service); + } + + static void ensureUses(Class clazz) { + // ServiceLoaderWrapper.class.getModule().addUses(aClass); + Class thisClass = ServiceLoaderWrapper.class; + ensureUses(thisClass, clazz); + } + + private static void ensureUses(Class thisClass, Class clazz) { + try { + final Class Module = Class.forName("java.lang.Module"); + final Method addUses = Module.getDeclaredMethod("addUses", Class.class); + final Method getModule = Class.class.getDeclaredMethod("getModule"); + final Object thisClassModule = getModule.invoke(thisClass); + addUses.invoke(thisClassModule, clazz); + } catch (SecurityException | ReflectiveOperationException t) { + //ignore - might log? + } + } + + public static class ModuleFinder { + + public static ModuleFinder of(Path... paths) { + return new ModuleFinder(); + } + + } + + public static class ModuleLayer { + + public static ModuleLayer boot() { + return new ModuleLayer(); + } + + public Configuration configuration() { + return new Configuration(); + } + + public ModuleLayer defineModulesWithOneLoader(Configuration cf, ClassLoader systemClassLoader) { + return new ModuleLayer(); + } + + public Set modules() { + return Collections.emptySet(); + } + } + + public static class Configuration { + + public Configuration resolveAndBind(ModuleFinder of, ModuleFinder finder, Set emptySet) { + return new Configuration(); + } + + } + + public static class ModuleDescriptor { + public static class Version { + public static void parse(String v) { + //TODO: do validation + } + } + } + +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ObjectsWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ObjectsWrapper.java new file mode 100644 index 000000000..db986df85 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ObjectsWrapper.java @@ -0,0 +1,62 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import static java.util.Objects.requireNonNull; + +public class ObjectsWrapper { + + public static T requireNonNullElse(T value, T elseValue) { + if (value != null) { + return value; + } + requireNonNull(elseValue, "elseValue"); + return elseValue; + } + + public static int checkFromIndexSize(int fromIndex, int size, int length) { + if ((length | fromIndex | size) < 0 || size > length - fromIndex) + throw new IndexOutOfBoundsException("" + fromIndex + ", " + size + ", " + length); + return fromIndex; + } +} + diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/PathWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/PathWrapper.java new file mode 100644 index 000000000..87bc0730d --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/PathWrapper.java @@ -0,0 +1,53 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; + +public class PathWrapper { + public static Path of(URI uri) { + //TODO: proper implementation: + return new File(uri).toPath(); + } +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/RuntimeWR.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/RuntimeWR.java new file mode 100644 index 000000000..1d36e4ad8 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/RuntimeWR.java @@ -0,0 +1,63 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.lang.IllegalStateException; +import java.lang.String; + +public final class RuntimeWR { + private RuntimeWR() { + } + + public static final class Version { + private Version() { + } + + public int feature() { + return 8; + } + + public static RuntimeWR.Version parse(String v) { + throw new IllegalStateException(); + } + } +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ServiceLoaderWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ServiceLoaderWrapper.java new file mode 100644 index 000000000..29afcf74c --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/ServiceLoaderWrapper.java @@ -0,0 +1,123 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.util.Iterator; +import java.util.ServiceLoader; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.tools.ToolProvider; + +public class ServiceLoaderWrapper implements Iterable { + private final ServiceLoader loader; + + public ServiceLoaderWrapper(ServiceLoader loader) { + this.loader = loader; + } + + public static ServiceLoader load(ModuleWrapper.ModuleLayer layer, Class aClass) { + ModuleWrapper.ensureUses(aClass); + return ServiceLoader.load(aClass); //XXX + } + + public static ServiceLoaderWrapper load(Class aClass) { + ModuleWrapper.ensureUses(aClass); + return new ServiceLoaderWrapper(ServiceLoader.load(aClass)); + } + + public static ServiceLoaderWrapper load(Class aClass, ClassLoader classLoader) { + ModuleWrapper.ensureUses(aClass); + return new ServiceLoaderWrapper(ServiceLoader.load(aClass, classLoader)); + } + + public static ServiceLoader loadWithClassLoader(Class aClass, ClassLoader classLoader) { + ModuleWrapper.ensureUses(aClass); + return ServiceLoader.load(aClass, classLoader); + } + + public static ServiceLoader loadTool(Class toolClass) { + ModuleWrapper.ensureUses(toolClass); + ServiceLoader res = ServiceLoader.load(toolClass, ToolProvider.class.getClassLoader()); + if (res.iterator().hasNext()) { + return res; + } + return ServiceLoader.load(toolClass, ClassLoader.getSystemClassLoader()); + } + + public Stream> stream() { + return StreamSupport.stream(loader.spliterator(), false) + .map(v -> new Provider() { + @Override + public Class type() { + return (Class) v.getClass(); + } + + @Override + public T get() { + return v; + } + }); + } + + @Override + public Iterator iterator() { + return loader.iterator(); + } + + @Override + public void forEach(Consumer action) { + loader.forEach(action); + } + + @Override + public Spliterator spliterator() { + return loader.spliterator(); + } + + public interface Provider { + public Class type(); + public T get(); + } + +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/StringWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/StringWrapper.java new file mode 100644 index 000000000..890d15fd6 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/StringWrapper.java @@ -0,0 +1,179 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class StringWrapper { + + public static String stripIndent(String str) { + //prototype only, needs testing and performance improvements: + String[] lines = lines(str); + int indent = Arrays.stream(lines) + .mapToInt((s) -> { + int at = 0; + while (at < s.length() && Character.isWhitespace(s.charAt(at))) { + at++; + } + if (at == s.length()) { + return Integer.MAX_VALUE; + } + return at; + }).min() + .orElse(Integer.MAX_VALUE); + if (lines.length > 0 && isBlank(lines[lines.length - 1])) { + indent = Math.min(indent, lines[lines.length - 1].length()); + } + int indentFin = indent; + return Arrays.stream(lines).map(line -> { + if (line.length() <= indentFin) { + return ""; + } + String l = line.substring(indentFin); + int at = l.length() - 1; + while (at >= 0 && Character.isWhitespace(l.charAt(at))) { + at--; + } + return l.substring(0, at + 1); + }).collect(Collectors.joining("\n")); + } + + public static String stripTrailing(String str) { + int end = str.length(); + while (end > 0 && Character.isWhitespace(str.charAt(end - 1))) + end--; + return str.substring(0, end); + } + + public static boolean isBlank(String s) { + int at = 0; + while (at < s.length() && Character.isWhitespace(s.charAt(at))) { + at++; + } + return at == s.length(); + } + + //copied from nb-javac: + public static String translateEscapes(String str) { + if (str.isEmpty()) { + return ""; + } + char[] chars = str.toCharArray(); + int length = chars.length; + int from = 0; + int to = 0; + while (from < length) { + char ch = chars[from++]; + if (ch == '\\') { + ch = from < length ? chars[from++] : '\0'; + switch (ch) { + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 's': + ch = ' '; + break; + case '\'': + case '\"': + case '\\': + // as is + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + int limit = Integer.min(from + (ch <= '3' ? 2 : 1), length); + int code = ch - '0'; + while (from < limit) { + ch = chars[from]; + if (ch < '0' || '7' < ch) { + break; + } + from++; + code = (code << 3) | (ch - '0'); + } + ch = (char)code; + break; + case '\n': continue; + default: { + String msg = String.format( + "Invalid escape sequence: \\%c \\\\u%04X", + ch, (int)ch); + throw new IllegalArgumentException(msg); + } + } + } + + chars[to++] = ch; + } + + return new String(chars, 0, to); + } + + private static String[] lines(String txt) { + List arr = new ArrayList<>(); + int at = 0; + for (;;) { + int next = txt.indexOf('\n', at); + if (next == -1) { + arr.add(txt.substring(at)); + break; + } + arr.add(txt.substring(at, next)); + at = next + 1; + } + return arr.toArray(new String[0]); + } +} diff --git a/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/VMWrapper.java b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/VMWrapper.java new file mode 100644 index 000000000..3f8979e2a --- /dev/null +++ b/subprojects/javac-jackpot/src/main/java/com/itsaky/androidide/javac/wrappers/VMWrapper.java @@ -0,0 +1,205 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.itsaky.androidide.javac.wrappers; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class VMWrapper { + private VMWrapper() { + } + + public static String[] getRuntimeArguments() { + return new String[0]; + } + + private static final String[] symbolFileLocation = { "lib", "ct.sym" }; + private static Reference cachedCtSym = new SoftReference<>(null); + + public static Path findCtSym() { + Path obj = cachedCtSym.get(); + if (obj instanceof Path) { + return obj; + } + try { + ClassLoader loader = VMWrapper.class.getClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + Enumeration en = loader.getResources("META-INF/services/com.sun.tools.javac.platform.PlatformProvider"); + URL res = en.hasMoreElements() ? en.nextElement() : null; + if (res == null) { + //runnning inside a JDK image, try to look for lib/ct.sym: + String javaHome = System.getProperty("java.home"); + Path file = Paths.get(javaHome); + for (String name : symbolFileLocation) { + file = file.resolve(name); + } + if (!Files.exists(file)) { + throw new IllegalStateException("Cannot find ct.sym at " + file); + } + return FileSystems.newFileSystem(file, (ClassLoader)null).getRootDirectories().iterator().next(); + } + if (!res.getProtocol().equals("jar")) { + res = en.hasMoreElements() ? en.nextElement() : null; + } + URL jar = ((JarURLConnection)res.openConnection()).getJarFileURL(); + Path path = Paths.get(jar.toURI()); + FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null); + Path ctSym = fs.getPath("META-INF", "ct.sym"); + cachedCtSym = new SoftReference<>(ctSym); + return ctSym; + } catch (IOException | URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + public static DirectoryStream newDirectoryStream(Path dir) throws IOException { + List all = new ArrayList<>(); + for (Path ch : Files.newDirectoryStream(dir)) { + final String fileName = ch.getFileName().toString(); + if (fileName.endsWith("/")) { + all.add(dir.resolve(fileName.substring(0, fileName.length() - 1))); + } else { + all.add(ch); + } + } + return new DirectoryStream() { + @Override + public Iterator iterator() { + return all.iterator(); + } + + @Override + public void close() { + } + }; + } + + public static FileSystem pathFs(Path p) { + return new FileSystem() { + @Override + public FileSystemProvider provider() { + return p.getFileSystem().provider(); + } + + @Override + public void close() throws IOException { + } + + @Override + public boolean isOpen() { + return p.getFileSystem().isOpen(); + } + + @Override + public boolean isReadOnly() { + return p.getFileSystem().isReadOnly(); + } + + @Override + public String getSeparator() { + return p.getFileSystem().getSeparator(); + } + + @Override + public Iterable getRootDirectories() { + return Collections.singleton(p); + } + + @Override + public Iterable getFileStores() { + return p.getFileSystem().getFileStores(); + } + + @Override + public Set supportedFileAttributeViews() { + return p.getFileSystem().supportedFileAttributeViews(); + } + + @Override + public Path getPath(String first, String... more) { + Path r = p.resolve(first); + for (String m : more) { + r = r.resolve(m); + } + return r; + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + return p.getFileSystem().getPathMatcher(syntaxAndPattern); + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + return p.getFileSystem().getUserPrincipalLookupService(); + } + + @Override + public WatchService newWatchService() throws IOException { + return p.getFileSystem().newWatchService(); + } + }; + } +} diff --git a/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.com.sun.tools.javac.platform.PlatformProvider b/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.com.sun.tools.javac.platform.PlatformProvider new file mode 100644 index 000000000..3bf4d445d --- /dev/null +++ b/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.com.sun.tools.javac.platform.PlatformProvider @@ -0,0 +1 @@ +com.itsaky.androidide.com.sun.tools.javac.platform.JDKPlatformProvider diff --git a/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.javax.tools.JavaCompiler b/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.javax.tools.JavaCompiler new file mode 100644 index 000000000..cfd9bf52d --- /dev/null +++ b/subprojects/javac-jackpot/src/main/resources/META-INF/services/com.itsaky.androidide.javax.tools.JavaCompiler @@ -0,0 +1 @@ +com.itsaky.androidide.com.sun.tools.javac.api.JavacTool \ No newline at end of file diff --git a/subprojects/javac-jackpot/src/main/resources/aa_nbjavac.hint b/subprojects/javac-jackpot/src/main/resources/aa_nbjavac.hint new file mode 100644 index 000000000..0aceabb3d --- /dev/null +++ b/subprojects/javac-jackpot/src/main/resources/aa_nbjavac.hint @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +java.util.Set.of($args$) => java.util.Collections.unmodifiableSet(new java.util.HashSet<>(java.util.Arrays.asList($args$)));; +java.util.List.of($args$) => java.util.Collections.unmodifiableList(java.util.Arrays.asList($args$));; +java.util.List.copyOf($list) => java.util.Collections.unmodifiableList(new java.util.ArrayList<>($list));; +java.util.Map.of($k, $v) => java.util.Collections.unmodifiableMap(java.util.Collections.singletonMap($k, $v));; + +//XXX: incorrect! +java.lang.Runtime.version().feature() => +"1.8".equals(System.getProperty("java.specification.version")) ? 8 : Integer.parseInt(System.getProperty("java.specification.version")) +;; + + +@java.io.Serial => ;; +@java.lang.Deprecated($arg1, $args$) => @java.lang.Deprecated;; + + +$1.flip() :: $1 instanceof java.nio.ByteBuffer +=> +((ByteBuffer) $1.flip()) +;; + +$1.clear() :: $1 instanceof java.nio.ByteBuffer +=> +((ByteBuffer) $1.clear()) +;; + +$1.flip() :: $1 instanceof java.nio.CharBuffer && !parentMatches("$1.flip();") +=> +((CharBuffer) $1.flip()) +;; + +java.nio.file.Path.of($uri) :: $uri instanceof java.net.URI +=> +com.itsaky.androidide.javac.wrappers.PathWrapper.of($uri) +;; + +java.lang.module.ModuleDescriptor +=> +com.itsaky.androidide.javac.wrappers.ModuleWrapper.ModuleDescriptor +;; + +$class.getModule() :: $class instanceof java.lang.Class +=> +com.itsaky.androidide.javac.wrappers.ModuleWrapper.getModule($class) +;; + +$cl.getUnnamedModule() :: $cl instanceof java.lang.ClassLoader +=> +com.itsaky.androidide.javac.wrappers.ModuleWrapper.getUnnamedModule($cl) +;; + +java.lang.Runtime.Version => com.itsaky.androidide.javac.wrappers.RuntimeWR.Version;; + +java.lang.Module => com.itsaky.androidide.javac.wrappers.ModuleWrapper;; + +java.lang.module.ModuleFinder => com.itsaky.androidide.javac.wrappers.ModuleWrapper.ModuleFinder;; +java.lang.ModuleLayer => com.itsaky.androidide.javac.wrappers.ModuleWrapper.ModuleLayer;; +java.lang.module.Configuration => com.itsaky.androidide.javac.wrappers.ModuleWrapper.Configuration;; + +java.util.ServiceLoader.load($layer, $class) :: $layer instanceof java.lang.ModuleLayer && $class instanceof java.lang.Class +=> +com.itsaky.androidide.javac.wrappers.ServiceLoaderWrapper.load($layer, $class) +;; + +java.util.ServiceLoader.load($class, $loader) :: $loader instanceof java.lang.ClassLoader && $class instanceof java.lang.Class && !inClass("com.itsaky.androidide.com.sun.tools.doclint.DocLint") && !inClass("com.itsaky.androidide.javax.tools.ToolProvider"); +=> +com.itsaky.androidide.javac.wrappers.ServiceLoaderWrapper.loadWithClassLoader($class, $loader) +;; + +java.util.ServiceLoader.load($class, $loader) :: $loader instanceof java.lang.ClassLoader && $class instanceof java.lang.Class && inClass("com.itsaky.androidide.javax.tools.ToolProvider"); +=> +com.itsaky.androidide.javac.wrappers.ServiceLoaderWrapper.loadTool($class) +;; + +jdk.internal.jmod.JmodFile => com.itsaky.androidide.javac.wrappers.JmodFileWrapper;; + +$1.isEmpty() :: $1 instanceof java.util.Optional +=> +!$1.isPresent() +;; + +java.util.Objects.requireNonNullElse($obj, $def) +=> +com.itsaky.androidide.javac.wrappers.ObjectsWrapper.requireNonNullElse($obj, $def) +;; + +java.util.Objects.checkFromIndexSize($from, $size, $len) +=> +com.itsaky.androidide.javac.wrappers.ObjectsWrapper.checkFromIndexSize($from, $size, $len) +;; + +java.util.ServiceLoader :: inClass("com.itsaky.androidide.com.sun.tools.doclint.DocLint") +=> +com.itsaky.androidide.javac.wrappers.ServiceLoaderWrapper +;; + +$1.stripTrailing() :: $1 instanceof java.lang.String +=> +$1.replaceAll("\\p{javaWhitespace}+$", "") +;; + +$1.stripLeading() :: $1 instanceof java.lang.String +=> +$1.replaceAll("^\\p{javaWhitespace}+", "") +;; + +$1.strip() :: $1 instanceof java.lang.String +=> +$1.replaceAll("^\\p{javaWhitespace}+", "").replaceAll("\\p{javaWhitespace}+$", "") +;; + +$1.stripIndent() :: $1 instanceof java.lang.String +=> +com.itsaky.androidide.javac.wrappers.StringWrapper.stripIndent($1) +;; + +$1.stripTrailing() //:: $1 instanceof java.lang.String +=> +com.itsaky.androidide.javac.wrappers.StringWrapper.stripTrailing($1) +;; + +$1.translateEscapes() :: $1 instanceof java.lang.String +=> +com.itsaky.androidide.javac.wrappers.StringWrapper.translateEscapes($1) +;; + +$1.isBlank() :: $1 instanceof java.lang.String +=> +com.itsaky.androidide.javac.wrappers.StringWrapper.isBlank($1) +;; + +$1.formatted($args$) :: $1 instanceof java.lang.String +=> +String.format($1, $args$) +;; + +jdk.internal.misc.VM => com.itsaky.androidide.javac.wrappers.VMWrapper;; + +java.lang.ClassLoader.getSystemClassLoader() :: !inClass("com.itsaky.androidide.javax.tools.ToolProvider") +=> com.itsaky.androidide.javax.tools.ToolProvider.class.getClassLoader();; + +//TODO: workaround for the NetBeans' approach to javac loading using two different ClassLoaders: +public static com.itsaky.androidide.com.sun.source.util.JavacTask instance(com.itsaky.androidide.javax.annotation.processing.ProcessingEnvironment processingEnvironment) {$whatever$;} +=> +public static com.itsaky.androidide.com.sun.source.util.JavacTask instance(com.itsaky.androidide.javax.annotation.processing.ProcessingEnvironment processingEnvironment) { + if (!processingEnvironment.getClass().getName().equals( + "com.itsaky.androidide.com.sun.tools.javac.processing.JavacProcessingEnvironment")) + throw new IllegalArgumentException(); + + try { + //Cannot refer to the implementation directly, needs to use reflection: + java.lang.reflect.Method m = processingEnvironment.getClass().getMethod("getJavacTask"); + + return (JavacTask) m.invoke(processingEnvironment); + } catch (NoSuchMethodException ex) { + throw new UnsupportedOperationException(ex); + } catch (SecurityException ex) { + throw new UnsupportedOperationException(ex); + } catch (IllegalAccessException ex) { + throw new UnsupportedOperationException(ex); + } catch (IllegalArgumentException ex) { + throw new UnsupportedOperationException(ex); + } catch (java.lang.reflect.InvocationTargetException ex) { + throw new UnsupportedOperationException(ex); + } +} +;; + +$1.resolve("jrt-fs.jar").toUri().toURL() :: $1 instanceof java.nio.file.Path +=> +$1.resolve("lib").resolve("jrt-fs.jar").toUri().toURL() +;; + + +com.itsaky.androidide.javax.tools.ToolProvider.getSystemTool($class, $module, $name) +=> +$class.cast(((java.util.function.Supplier) () -> { + try { + Object v; + try { + v = com.itsaky.androidide.javax.tools.ToolProvider.getSystemTool($class, $module, $name); + } catch (Error err) { + v = null; + } + if (v == null) { + v = Class.forName($name).newInstance(); + } + return $class.cast(v); + } catch (Throwable t) { + throw new Error(t); + } +}).get()) +;; + +static java.nio.file.Path findCtSym() { + $code$ +} +=> +static java.nio.file.Path findCtSym() { + return com.itsaky.androidide.javac.wrappers.VMWrapper.findCtSym() +} +;; + +java.nio.file.FileSystems.newFileSystem($path, $a2) +:: inClass("com.itsaky.androidide.com.sun.tools.javac.platform.JDKPlatformProvider") +=> +com.itsaky.androidide.javac.wrappers.VMWrapper.pathFs($path) +;; + +/* +String $v = Character.toString($ch); :: $ch instanceof char +=> +if (!Character.isJavaIdentifierPart($ch)) { + continue; +} +String $v = "" + $ch; +;; +*/ + +java.nio.file.Files.newDirectoryStream($a1) +:: inClass("com.itsaky.androidide.com.sun.tools.javac.platform.JDKPlatformProvider") +=> +com.itsaky.androidide.javac.wrappers.VMWrapper.newDirectoryStream($a1) +;; + +$1.toList() :: $1 instanceof java.util.stream.Stream +=> +$1.collect(java.util.stream.Collectors.toList()) +;; + +//to eliminate imports: +com.itsaky.androidide.jdk.internal.javac.PreviewFeature +=> +java.lang.VirtualMachineError +;; + +//to eliminate imports: +jdk.internal.javac.NoPreview +=> +java.lang.VirtualMachineError +;; + +//to eliminate imports: +java.io.Serial +=> +java.lang.VirtualMachineError +;; + +// fallback to highest available ct.sym +com.itsaky.androidide.com.sun.tools.javac.platform.PlatformUtils.lookupPlatformDescription($s) +=> +((java.util.function.Supplier) () -> { + final String version = $s; + com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription pd = com.itsaky.androidide.com.sun.tools.javac.platform.PlatformUtils.lookupPlatformDescription(version); + if (pd != null) { + return pd; + } + + com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription fallback = null; + for (int numVersion = Integer.valueOf(version); fallback == null; numVersion--) { + fallback = PlatformUtils.lookupPlatformDescription("" + numVersion); + } + if (fallback == null) { + throw new NullPointerException("No platform found for " + version); + } + final com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription delegate = fallback; + + return new com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription() { + @Override + public com.itsaky.androidide.javax.tools.JavaFileManager getFileManager() { + return delegate.getFileManager(); + } + + @Override + public String getSourceVersion() { + return version; + } + + @Override + public String getTargetVersion() { + return version; + } + + @Override + public java.util.List> getAnnotationProcessors() { + return delegate.getAnnotationProcessors(); + } + + @Override + public java.util.List> getPlugins() { + return delegate.getPlugins(); + } + + @Override + public java.util.List getAdditionalOptions() { + return delegate.getAdditionalOptions(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + }; +}).get(); +;; + +$cs.isEmpty() :: $cs instanceof java.lang.CharSequence +=> +($cs.length() == 0) +;; + +$coll.toArray($type[]::new) :: inClass("com.itsaky.androidide.com.sun.tools.javac.comp.Flow") +=> +$coll.toArray(new $type[0]) +;; + +$coll.toArray($expr) :: inClass("com.itsaky.androidide.com.sun.tools.javac.comp.Flow") && containsAny($expr, "new com.itsaky.androidide.com.sun.tools.javac.code.Type[$s]") +=> +$coll.toArray(new com.itsaky.androidide.com.sun.tools.javac.code.Type[0]) +;; + + +Iterable $it; +try { + $any$; +} catch $catches$ :: inClass("com.itsaky.androidide.com.sun.tools.javac.main.Main") +=> +Iterable $it = java.util.Arrays.asList(argv); +;; + +jdk.internal.opt.CommandLine +=> +java.lang.VirtualMachineError +;; + +jdk.internal.opt.CommandLine.UnmatchedQuote +=> +java.lang.VirtualMachineError +;; diff --git a/subprojects/javac-jackpot/src/main/resources/ab_sun.reflect.annotation.hint b/subprojects/javac-jackpot/src/main/resources/ab_sun.reflect.annotation.hint new file mode 100644 index 000000000..fc2437829 --- /dev/null +++ b/subprojects/javac-jackpot/src/main/resources/ab_sun.reflect.annotation.hint @@ -0,0 +1,41 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +sun.reflect.annotation.ExceptionProxy +=> +com.itsaky.androidide.sun.reflect.annotation.ExceptionProxy +;; + +sun.reflect.annotation.AnnotationParser +=> +com.itsaky.androidide.sun.reflect.annotation.AnnotationParser +;; + +sun.reflect.annotation.EnumConstantNotPresentExceptionProxy +=> +com.itsaky.androidide.sun.reflect.annotation.EnumConstantNotPresentExceptionProxy +;; + +sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy +=> +com.itsaky.androidide.sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy +;; + +sun.reflect.annotation.AnnotationType.invocationHandlerReturnType($returnClass) +=> +com.itsaky.androidide.sun.reflect.annotation.AnnotationTypeAccess.invocationHandlerReturnType($returnClass) +;;