feat(javac-21): add support for Java 21 in the IDE (WIP)

This commit is contained in:
Akash Yadav
2024-01-28 09:42:17 +05:30
parent 237469bd91
commit e9269f0d00
39 changed files with 3933 additions and 67 deletions
+2
View File
@@ -46,4 +46,6 @@ dependencies {
implementation(libs.common.antlr4) implementation(libs.common.antlr4)
implementation(libs.google.gson) implementation(libs.google.gson)
implementation(libs.google.java.format) implementation(libs.google.java.format)
implementation(libs.misc.jackpot30)
} }
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Project> {
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 <module>/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()
)
}
}
}
}
@@ -41,32 +41,32 @@ class PropertiesParserPlugin : Plugin<Project> {
sourceSets.getByName("main").java { srcDir(target.propsDir) } sourceSets.getByName("main").java { srcDir(target.propsDir) }
} }
target.tasks.register("compileProperties") { // target.tasks.register("compileProperties") {
doLast { // doLast {
val options = target.createParserOptions() // val options = target.createParserOptions()
if (options.isEmpty()) { // if (options.isEmpty()) {
return@doLast // return@doLast
} // }
//
val parser = PropertiesParser { target.logger.lifecycle(it) } // val parser = PropertiesParser { target.logger.lifecycle(it) }
//
logger.info( // logger.info(
"Running PropertiesParser with arguments: ${options.joinToString(separator = " ")}" // "Running PropertiesParser with arguments: ${options.joinToString(separator = " ")}"
) // )
//
val ok = parser.run(options.toTypedArray()) // val ok = parser.run(options.toTypedArray())
if (!ok) { // if (!ok) {
throw GradleException("Failed to parse property files") // throw GradleException("Failed to parse property files")
} // }
} // }
//
outputs.dir(target.propsDir) // outputs.dir(target.propsDir)
} // }
//
target.tasks.register("cleanCompiledProperties", Delete::class.java) { delete(target.propsDir) } // target.tasks.register("cleanCompiledProperties", Delete::class.java) { delete(target.propsDir) }
//
target.tasks.withType(JavaCompile::class.java) { dependsOn("compileProperties") } // target.tasks.withType(JavaCompile::class.java) { dependsOn("compileProperties") }
target.tasks.getByName("clean").dependsOn("cleanCompiledProperties") // target.tasks.getByName("clean").dependsOn("cleanCompiledProperties")
} }
} }
@@ -72,6 +72,7 @@ fun Project.configureAndroidModule(
arrayOf( arrayOf(
"META-INF/CHANGES", "META-INF/CHANGES",
"META-INF/README.md", "META-INF/README.md",
"nbjavac.hint"
) )
) )
pickFirsts.addAll( pickFirsts.addAll(
@@ -46,20 +46,11 @@ public class PropertiesParser {
public Logger logger; public Logger logger;
public PropertiesParser(Logger logger) { private final File baseDir;
public PropertiesParser(Logger logger, File baseDir) {
this.logger = logger; this.logger = logger;
} this.baseDir = baseDir;
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);
} }
public void info(String msg) { public void info(String msg) {
@@ -86,7 +77,7 @@ public class PropertiesParser {
File propertyFile = new File(propertyPath); File propertyFile = new File(propertyPath);
String prefix = propertyFile.getName().split("\\.")[0]; String prefix = propertyFile.getName().split("\\.")[0];
MessageFile messageFile = new MessageFile(propertyFile, prefix); 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) { } catch (Throwable ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
@@ -57,6 +57,8 @@ import java.util.stream.Stream;
public class ClassGenerator { public class ClassGenerator {
private final File baseDir;
private static final String TEMPLATE_PROPS = "#\n" + private static final String TEMPLATE_PROPS = "#\n" +
"# Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.\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" + "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" +
@@ -86,10 +88,10 @@ public class ClassGenerator {
" package {0};\\n\\\n" + " package {0};\\n\\\n" +
" \\n\\\n" + " \\n\\\n" +
" {1}\\n\\\n" + " {1}\\n\\\n" +
" import openjdk.tools.javac.util.JCDiagnostic.Error;\\n\\\n" + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Error;\\n\\\n" +
" import openjdk.tools.javac.util.JCDiagnostic.Warning;\\n\\\n" + " import com.itsaky.androidide.com.sun.tools.javac.util.JCDiagnostic.Warning;\\n\\\n" +
" import openjdk.tools.javac.util.JCDiagnostic.Note;\\n\\\n" + " import com.itsaky.androidide.com.sun.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.Fragment;\\n\\\n" +
" \\n\\\n" + " \\n\\\n" +
" public class {2} '{'\\n\\\n" + " public class {2} '{'\\n\\\n" +
" {3}\\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. * Form the name of the toplevel factory class.
*/ */
@@ -337,9 +343,7 @@ public class ClassGenerator {
*/ */
String packageName(File file) { String packageName(File file) {
String path = file.getAbsolutePath(); String path = file.getAbsolutePath();
int begin = path.indexOf("src/main/resources".replace('/', File.separatorChar)) String packagePath = path.substring(baseDir.getAbsolutePath().length() + 1, path.lastIndexOf(File.separatorChar));
+ "src/main/resources".length();
String packagePath = path.substring(begin + 1, path.lastIndexOf(File.separatorChar));
return packagePath.replace(File.separatorChar, '.'); return packagePath.replace(File.separatorChar, '.');
} }
@@ -46,30 +46,30 @@ public interface MessageType {
*/ */
enum SimpleType implements 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), BOOLEAN("boolean", "boolean", null),
COLLECTION("collection", "Collection", "java.util"), 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), FRAGMENT("fragment", "Fragment", null),
DIAGNOSTIC("diagnostic", "JCDiagnostic", "openjdk.tools.javac.util"), DIAGNOSTIC("diagnostic", "JCDiagnostic", "com.itsaky.androidide.com.sun.tools.javac.util"),
MODIFIER("modifier", "Modifier", "jdkx.lang.model.element"), MODIFIER("modifier", "Modifier", "com.itsaky.androidide.javax.lang.model.element"),
FILE("file", "File", "java.io"), 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"), 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), NUMBER("number", "int", null),
OPTION_NAME("option name", "Option", "openjdk.tools.javac.main"), OPTION_NAME("option name", "Option", "com.itsaky.androidide.com.sun.tools.javac.main"),
PROFILE("profile", "Profile", "openjdk.tools.javac.jvm"), PROFILE("profile", "Profile", "com.itsaky.androidide.com.sun.tools.javac.jvm"),
SOURCE("source", "Source", "openjdk.tools.javac.code"), SOURCE("source", "Source", "com.itsaky.androidide.com.sun.tools.javac.code"),
SOURCE_VERSION("source version", "SourceVersion", "jdkx.lang.model"), SOURCE_VERSION("source version", "SourceVersion", "com.itsaky.androidide.javax.lang.model"),
STRING("string", "String", null), STRING("string", "String", null),
SYMBOL("symbol", "Symbol", "openjdk.tools.javac.code"), SYMBOL("symbol", "Symbol", "com.itsaky.androidide.com.sun.tools.javac.code"),
SYMBOL_KIND("symbol kind", "Kind", "openjdk.tools.javac.code.Kinds"), SYMBOL_KIND("symbol kind", "Kind", "com.itsaky.androidide.com.sun.tools.javac.code.Kinds"),
KIND_NAME("kind name", "KindName", "openjdk.tools.javac.code.Kinds"), KIND_NAME("kind name", "KindName", "com.itsaky.androidide.com.sun.tools.javac.code.Kinds"),
TARGET("target", "Target", "openjdk.tools.javac.jvm"), TARGET("target", "Target", "com.itsaky.androidide.com.sun.tools.javac.jvm"),
TOKEN("token", "TokenKind", "openjdk.tools.javac.parser.Tokens"), TOKEN("token", "TokenKind", "com.itsaky.androidide.com.sun.tools.javac.parser.Tokens"),
TREE_TAG("tree tag", "Tag", "openjdk.tools.javac.tree.JCTree"), TREE_TAG("tree tag", "Tag", "com.itsaky.androidide.com.sun.tools.javac.tree.JCTree"),
TYPE("type", "Type", "openjdk.tools.javac.code"), TYPE("type", "Type", "com.itsaky.androidide.com.sun.tools.javac.code"),
URL("url", "URL", "java.net"), URL("url", "URL", "java.net"),
SET("set", "Set", "java.util"), SET("set", "Set", "java.util"),
LIST("list", "List", "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 * handy option when prototyping - but usages of custom types should be avoided in product-quality
* resource file comments. * resource file comments.
* <p> * <p>
* Example: 'openjdk.tools.javac.code.Flags.Flag' * Example: 'com.itsaky.androidide.com.sun.tools.javac.code.Flags.Flag'
*/ */
class CustomType implements MessageType { class CustomType implements MessageType {
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Pair<String, String>>
@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)
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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() {
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<File> {
return compilerModules().map { resolveTransformedSrcDir(sourceOutDir, it, false) }
}
private fun compilerModules(): Array<String> {
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<String>,
srcOut: File): MutableList<String> {
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
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> {
val options = mutableListOf<String>()
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
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<JavacRenamePackgeReferencesWorker.Params> {
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
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
/**
* The SHA-256 hash of the source tarball.
*/
@get:Input
abstract val sourceSHA256: Property<String>
/**
* 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()}"
}
}
@@ -42,7 +42,7 @@ object DownloadUtils {
logger: Logger 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") val digest = MessageDigest.getInstance("SHA-256")
+3 -1
View File
@@ -22,7 +22,6 @@ maven-publish-plugin = "0.25.3"
[libraries] [libraries]
# AndroidIDE # 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 = { 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-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" } 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" } 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" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" }
+6
View File
@@ -1,5 +1,11 @@
@file:Suppress("UnstableApiUsage") @file:Suppress("UnstableApiUsage")
include(":subprojects:javac-deps")
include(":subprojects:javac-jackpot")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement { pluginManagement {
+1
View File
@@ -0,0 +1 @@
/build
+26
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
@Suppress("")
plugins {
id("java-library")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
@@ -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<? extends Annotation> type;
@SuppressWarnings("serial") // Not statically typed as Serializable
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> 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(),
"<no canonical name>"));
result.append('(');
boolean firstMember = true;
Set<Map.Entry<String, Object>> entries = memberValues.entrySet();
boolean loneValue = entries.size() == 1;
for (Map.Entry<String, Object> 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<String> 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(),
"<no canonical name>") + ".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<String> convert(byte[] values) {
List<String> list = new ArrayList<>(values.length);
for (byte b : values)
list.add(toSourceString(b));
return list.stream();
}
private static Stream<String> convert(char[] values) {
List<String> list = new ArrayList<>(values.length);
for (char c : values)
list.add(toSourceString(c));
return list.stream();
}
private static Stream<String> convert(float[] values) {
List<String> list = new ArrayList<>(values.length);
for (float f : values) {
list.add(toSourceString(f));
}
return list.stream();
}
private static Stream<String> convert(short[] values) {
List<String> list = new ArrayList<>(values.length);
for (short s : values)
list.add(Short.toString(s));
return list.stream();
}
private static Stream<String> convert(boolean[] values) {
List<String> list = new ArrayList<>(values.length);
for (boolean b : values)
list.add(Boolean.toString(b));
return list.stream();
}
private static String stringStreamToString(Stream<String> 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<Method[]>() {
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<? extends Annotation> 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<String, Object> 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<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
@SuppressWarnings("unchecked")
Map<String, Object> streamVals = (Map<String, Object>)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<String, Class<?>> memberTypes = annotationType.memberTypes();
// consistent with runtime Map type
Map<String, Object> mv = new LinkedHashMap<>();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> 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<String, Object> 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<? extends Annotation> type) {
unsafe.putReference(o, typeOffset, type);
}
static void setMemberValues(AnnotationInvocationHandler o,
Map<String, Object> memberValues) {
unsafe.putReference(o, memberValuesOffset, memberValues);
}
}
}
@@ -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<Class<? extends Annotation>, 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).<p>
* 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<Class<? extends Annotation>, Annotation> parseSelectAnnotations(
byte[] rawAnnotations,
ConstantPool constPool,
Class<?> container,
Class<? extends Annotation> ... 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<Class<? extends Annotation>, Annotation> parseAnnotations2(
byte[] rawAnnotations,
ConstantPool constPool,
Class<?> container,
Class<? extends Annotation>[] selectAnnotationClasses) {
Map<Class<? extends Annotation>, Annotation> result =
new LinkedHashMap<Class<? extends Annotation>, 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<? extends Annotation> 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<Annotation> annotations =
new ArrayList<Annotation>(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<? extends Annotation>[] selectAnnotationClasses) {
int typeIndex = buf.getShort() & 0xFFFF;
Class<? extends Annotation> annotationClass = null;
String sig = "[unknown]";
try {
sig = constPool.getUTF8At(typeIndex);
annotationClass = (Class<? extends Annotation>)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<String, Class<?>> memberTypes = type.memberTypes();
Map<String, Object> memberValues =
new LinkedHashMap<String, Object>(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<? extends Annotation> type,
final Map<String, Object> memberValues)
{
return AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
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<? extends Enum<?>>)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<? extends Enum> 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<? extends Enum<?>>)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<? extends Enum<?>>)componentType, buf,
constPool, container);
} else if (componentType.isAnnotation()) {
return parseAnnotationArray(length, (Class <? extends Annotation>)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<? extends Enum<?>> 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<? extends Annotation> 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<Object> 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<Class<? extends Annotation>, Annotation> annotations) {
return annotations.values().toArray(EMPTY_ANNOTATION_ARRAY);
}
static Annotation[] getEmptyAnnotationArray() { return EMPTY_ANNOTATION_ARRAY; }
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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 + "\" */" ;
}
}
@@ -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<? extends Enum<?>> enumType;
final String constName;
public EnumConstantNotPresentExceptionProxy(Class<? extends Enum<?>> 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! */";
}
}
@@ -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();
}
+1
View File
@@ -0,0 +1 @@
/build
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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 <S> void addUses(Class<S> 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<ModuleWrapper> 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
}
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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> 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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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();
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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();
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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<T> implements Iterable<T> {
private final ServiceLoader<T> loader;
public ServiceLoaderWrapper(ServiceLoader<T> loader) {
this.loader = loader;
}
public static <T> ServiceLoader<T> load(ModuleWrapper.ModuleLayer layer, Class<T> aClass) {
ModuleWrapper.ensureUses(aClass);
return ServiceLoader.load(aClass); //XXX
}
public static <T> ServiceLoaderWrapper<T> load(Class<T> aClass) {
ModuleWrapper.ensureUses(aClass);
return new ServiceLoaderWrapper<T>(ServiceLoader.load(aClass));
}
public static <T> ServiceLoaderWrapper<T> load(Class<T> aClass, ClassLoader classLoader) {
ModuleWrapper.ensureUses(aClass);
return new ServiceLoaderWrapper<T>(ServiceLoader.load(aClass, classLoader));
}
public static <T> ServiceLoader<T> loadWithClassLoader(Class<T> aClass, ClassLoader classLoader) {
ModuleWrapper.ensureUses(aClass);
return ServiceLoader.load(aClass, classLoader);
}
public static <T> ServiceLoader<T> loadTool(Class<T> toolClass) {
ModuleWrapper.ensureUses(toolClass);
ServiceLoader<T> res = ServiceLoader.load(toolClass, ToolProvider.class.getClassLoader());
if (res.iterator().hasNext()) {
return res;
}
return ServiceLoader.load(toolClass, ClassLoader.getSystemClassLoader());
}
public Stream<Provider<T>> stream() {
return StreamSupport.stream(loader.spliterator(), false)
.map(v -> new Provider<T>() {
@Override
public Class<? extends T> type() {
return (Class<T>) v.getClass();
}
@Override
public T get() {
return v;
}
});
}
@Override
public Iterator<T> iterator() {
return loader.iterator();
}
@Override
public void forEach(Consumer<? super T> action) {
loader.forEach(action);
}
@Override
public Spliterator<T> spliterator() {
return loader.spliterator();
}
public interface Provider<T> {
public Class<? extends T> type();
public T get();
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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<String> 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]);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
/*
* 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<Path> 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<URL> 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<Path> newDirectoryStream(Path dir) throws IOException {
List<Path> 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<Path>() {
@Override
public Iterator<Path> 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<Path> getRootDirectories() {
return Collections.singleton(p);
}
@Override
public Iterable<FileStore> getFileStores() {
return p.getFileSystem().getFileStores();
}
@Override
public Set<String> 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();
}
};
}
}
@@ -0,0 +1 @@
com.itsaky.androidide.com.sun.tools.javac.platform.JDKPlatformProvider
@@ -0,0 +1 @@
com.itsaky.androidide.com.sun.tools.javac.api.JavacTool
@@ -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<Object>) () -> {
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<com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription>) () -> {
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<PlatformDescription.PluginInfo<com.itsaky.androidide.javax.annotation.processing.Processor>> getAnnotationProcessors() {
return delegate.getAnnotationProcessors();
}
@Override
public java.util.List<com.itsaky.androidide.com.sun.tools.javac.platform.PlatformDescription.PluginInfo<com.itsaky.androidide.com.sun.source.util.Plugin>> getPlugins() {
return delegate.getPlugins();
}
@Override
public java.util.List<String> 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<String> $it;
try {
$any$;
} catch $catches$ :: inClass("com.itsaky.androidide.com.sun.tools.javac.main.Main")
=>
Iterable<String> $it = java.util.Arrays.asList(argv);
;;
jdk.internal.opt.CommandLine
=>
java.lang.VirtualMachineError
;;
jdk.internal.opt.CommandLine.UnmatchedQuote
=>
java.lang.VirtualMachineError
;;
@@ -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 <https://www.gnu.org/licenses/>.
*/
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)
;;